From cb2ee0451d5d919cd4c45773299f1c2d8f5565de Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 09:06:01 +0100 Subject: [PATCH 001/159] move Settings watchers over to an ES6 Map --- src/settings/SettingsStore.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index c32bbe731d..25bc23f572 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -26,7 +26,7 @@ import { _t } from '../languageHandler'; import dis from '../dispatcher/dispatcher'; import { ISetting, SETTINGS } from "./Settings"; import LocalEchoWrapper from "./handlers/LocalEchoWrapper"; -import { WatchManager } from "./WatchManager"; +import { WatchManager, CallbackFn as WatchCallbackFn } from "./WatchManager"; import { SettingLevel } from "./SettingLevel"; import SettingsHandler from "./handlers/SettingsHandler"; @@ -117,7 +117,7 @@ export default class SettingsStore { // We also maintain a list of monitors which are special watchers: they cause dispatches // when the setting changes. We track which rooms we're monitoring though to ensure we // don't duplicate updates on the bus. - private static watchers = {}; // { callbackRef => { callbackFn } } + private static watchers = new Map(); private static monitors = {}; // { settingName => { roomId => callbackRef } } // Counter used for generation of watcher IDs @@ -163,7 +163,7 @@ export default class SettingsStore { callbackFn(originalSettingName, changedInRoomId, atLevel, newValAtLevel, newValue); }; - SettingsStore.watchers[watcherId] = localizedCallback; + SettingsStore.watchers.set(watcherId, localizedCallback); defaultWatchManager.watchSetting(settingName, roomId, localizedCallback); return watcherId; @@ -176,13 +176,13 @@ export default class SettingsStore { * to cancel. */ public static unwatchSetting(watcherReference: string) { - if (!SettingsStore.watchers[watcherReference]) { + if (!SettingsStore.watchers.has(watcherReference)) { console.warn(`Ending non-existent watcher ID ${watcherReference}`); return; } - defaultWatchManager.unwatchSetting(SettingsStore.watchers[watcherReference]); - delete SettingsStore.watchers[watcherReference]; + defaultWatchManager.unwatchSetting(SettingsStore.watchers.get(watcherReference)); + SettingsStore.watchers.delete(watcherReference); } /** From cf501371fa313885c551b650d0959608be5b1f64 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 09:11:14 +0100 Subject: [PATCH 002/159] move Settings monitors over to an ES6 Map --- src/settings/SettingsStore.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/settings/SettingsStore.ts b/src/settings/SettingsStore.ts index 25bc23f572..e1e300e185 100644 --- a/src/settings/SettingsStore.ts +++ b/src/settings/SettingsStore.ts @@ -118,7 +118,7 @@ export default class SettingsStore { // when the setting changes. We track which rooms we're monitoring though to ensure we // don't duplicate updates on the bus. private static watchers = new Map(); - private static monitors = {}; // { settingName => { roomId => callbackRef } } + private static monitors = new Map>(); // { settingName => { roomId => callbackRef } } // Counter used for generation of watcher IDs private static watcherCount = 1; @@ -196,10 +196,10 @@ export default class SettingsStore { public static monitorSetting(settingName: string, roomId: string) { roomId = roomId || null; // the thing wants null specifically to work, so appease it. - if (!this.monitors[settingName]) this.monitors[settingName] = {}; + if (!this.monitors.has(settingName)) this.monitors.set(settingName, new Map()); const registerWatcher = () => { - this.monitors[settingName][roomId] = SettingsStore.watchSetting( + this.monitors.get(settingName).set(roomId, SettingsStore.watchSetting( settingName, roomId, (settingName, inRoomId, level, newValueAtLevel, newValue) => { dis.dispatch({ action: 'setting_updated', @@ -210,19 +210,20 @@ export default class SettingsStore { newValue, }); }, - ); + )); }; - const hasRoom = Object.keys(this.monitors[settingName]).find((r) => r === roomId || r === null); + const rooms = Array.from(this.monitors.get(settingName).keys()); + const hasRoom = rooms.find((r) => r === roomId || r === null); if (!hasRoom) { registerWatcher(); } else { if (roomId === null) { // Unregister all existing watchers and register the new one - for (const roomId of Object.keys(this.monitors[settingName])) { - SettingsStore.unwatchSetting(this.monitors[settingName][roomId]); - } - this.monitors[settingName] = {}; + rooms.forEach(roomId => { + SettingsStore.unwatchSetting(this.monitors.get(settingName).get(roomId)); + }); + this.monitors.get(settingName).clear(); registerWatcher(); } // else a watcher is already registered for the room, so don't bother registering it again } From 73b24ae2251f144d8bacd93492285085add34577 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 19 May 2021 09:24:46 +0100 Subject: [PATCH 003/159] move WatchManager over to an ES6 Map --- src/settings/WatchManager.ts | 44 ++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/src/settings/WatchManager.ts b/src/settings/WatchManager.ts index ea2f158ef6..56f911f180 100644 --- a/src/settings/WatchManager.ts +++ b/src/settings/WatchManager.ts @@ -18,11 +18,7 @@ import { SettingLevel } from "./SettingLevel"; export type CallbackFn = (changedInRoomId: string, atLevel: SettingLevel, newValAtLevel: any) => void; -const IRRELEVANT_ROOM: string = null; - -interface RoomWatcherMap { - [roomId: string]: CallbackFn[]; -} +const IRRELEVANT_ROOM = Symbol("irrelevant-room"); /** * Generalized management class for dealing with watchers on a per-handler (per-level) @@ -30,25 +26,25 @@ interface RoomWatcherMap { * class, which are then proxied outwards to any applicable watchers. */ export class WatchManager { - private watchers: {[settingName: string]: RoomWatcherMap} = {}; + private watchers = new Map>(); // settingName -> roomId -> CallbackFn[] // Proxy for handlers to delegate changes to this manager public watchSetting(settingName: string, roomId: string | null, cb: CallbackFn) { - if (!this.watchers[settingName]) this.watchers[settingName] = {}; - if (!this.watchers[settingName][roomId]) this.watchers[settingName][roomId] = []; - this.watchers[settingName][roomId].push(cb); + if (!this.watchers.has(settingName)) this.watchers.set(settingName, new Map()); + if (!this.watchers.get(settingName).has(roomId)) this.watchers.get(settingName).set(roomId, []); + this.watchers.get(settingName).get(roomId).push(cb); } // Proxy for handlers to delegate changes to this manager public unwatchSetting(cb: CallbackFn) { - for (const settingName of Object.keys(this.watchers)) { - for (const roomId of Object.keys(this.watchers[settingName])) { + this.watchers.forEach((map) => { + map.forEach((callbacks) => { let idx; - while ((idx = this.watchers[settingName][roomId].indexOf(cb)) !== -1) { - this.watchers[settingName][roomId].splice(idx, 1); + while ((idx = callbacks.indexOf(cb)) !== -1) { + callbacks.splice(idx, 1); } - } - } + }); + }); } public notifyUpdate(settingName: string, inRoomId: string | null, atLevel: SettingLevel, newValueAtLevel: any) { @@ -56,21 +52,21 @@ export class WatchManager { // we also don't have a reliable way to get the old value of a setting. Instead, we'll just // let it fall through regardless and let the receiver dedupe if they want to. - if (!this.watchers[settingName]) return; + if (!this.watchers.has(settingName)) return; - const roomWatchers = this.watchers[settingName]; + const roomWatchers = this.watchers.get(settingName); const callbacks = []; - if (inRoomId !== null && roomWatchers[inRoomId]) { - callbacks.push(...roomWatchers[inRoomId]); + if (inRoomId !== null && roomWatchers.has(inRoomId)) { + callbacks.push(...roomWatchers.get(inRoomId)); } 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).flat(1)); - } else if (roomWatchers[IRRELEVANT_ROOM]) { - callbacks.push(...roomWatchers[IRRELEVANT_ROOM]); + // Fire updates to all the individual room watchers too, as they probably care about the change higher up. + const callbacks = Array.from(roomWatchers.values()).flat(1); + callbacks.push(...callbacks); + } else if (roomWatchers.has(IRRELEVANT_ROOM)) { + callbacks.push(...roomWatchers.get(IRRELEVANT_ROOM)); } for (const callback of callbacks) { From 8a3db3b40ac466c17368ba24571f06820ac84db7 Mon Sep 17 00:00:00 2001 From: Nique Woodhouse Date: Wed, 19 May 2021 10:25:54 +0100 Subject: [PATCH 004/159] Update _MessageActionBar.scss Refine UI of message action bar to increase usability and focus on bar content. --- res/css/views/messages/_MessageActionBar.scss | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index d41ac3a4ba..a697c8d0be 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -20,11 +20,12 @@ limitations under the License. visibility: hidden; cursor: pointer; display: flex; - height: 24px; + height: 28px; line-height: $font-24px; - border-radius: 4px; - background: $message-action-bar-bg-color; - top: -26px; + border-radius: 8px; + background: $primary-bg-color; + border: 1px solid $input-border-color; + top: -28px; right: 8px; user-select: none; // Ensure the action bar appears above over things, like the read marker. @@ -51,31 +52,19 @@ limitations under the License. white-space: nowrap; display: inline-block; position: relative; - border: 1px solid $message-action-bar-border-color; - margin-left: -1px; + margin: 2px; &:hover { - border-color: $message-action-bar-hover-border-color; + background: $roomlist-button-bg-color; + border-radius: 6px; z-index: 1; } - - &:first-child { - border-radius: 3px 0 0 3px; - } - - &:last-child { - border-radius: 0 3px 3px 0; - } - - &:only-child { - border-radius: 3px; - } } } - .mx_MessageActionBar_maskButton { - width: 27px; + width: 24px; + height: 24px; } .mx_MessageActionBar_maskButton::after { @@ -88,7 +77,11 @@ limitations under the License. mask-size: 18px; mask-repeat: no-repeat; mask-position: center; - background-color: $message-action-bar-fg-color; + background-color: $secondary-fg-color; +} + +.mx_MessageActionBar_maskButton:hover:after { + background-color: $primary-fg-color; } .mx_MessageActionBar_reactButton::after { From 5daca41bfb049c12ecdb5edfb326f5c257287578 Mon Sep 17 00:00:00 2001 From: Nique Woodhouse Date: Wed, 19 May 2021 11:17:34 +0100 Subject: [PATCH 005/159] Update _MessageActionBar.scss Increase size of buttons --- res/css/views/messages/_MessageActionBar.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index a697c8d0be..81be6d1917 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -20,12 +20,12 @@ limitations under the License. visibility: hidden; cursor: pointer; display: flex; - height: 28px; + height: 32px; line-height: $font-24px; border-radius: 8px; background: $primary-bg-color; border: 1px solid $input-border-color; - top: -28px; + top: -32px; right: 8px; user-select: none; // Ensure the action bar appears above over things, like the read marker. @@ -63,8 +63,8 @@ limitations under the License. } .mx_MessageActionBar_maskButton { - width: 24px; - height: 24px; + width: 28px; + height: 28px; } .mx_MessageActionBar_maskButton::after { From e6c595a5635cec2eb48c84526c7dd66c0132eb9a Mon Sep 17 00:00:00 2001 From: Germain Date: Wed, 19 May 2021 11:27:32 +0100 Subject: [PATCH 006/159] Delete RoomView dead code --- src/components/structures/RoomView.tsx | 45 -------------------------- 1 file changed, 45 deletions(-) diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index c0f3c59457..7c9afacd55 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -1598,33 +1598,6 @@ export default class RoomView extends React.Component { this.setState({auxPanelMaxHeight: auxPanelMaxHeight}); }; - private onFullscreenClick = () => { - dis.dispatch({ - action: 'video_fullscreen', - fullscreen: true, - }, true); - }; - - private onMuteAudioClick = () => { - const call = this.getCallForRoom(); - if (!call) { - return; - } - const newState = !call.isMicrophoneMuted(); - call.setMicrophoneMuted(newState); - this.forceUpdate(); // TODO: just update the voip buttons - }; - - private onMuteVideoClick = () => { - const call = this.getCallForRoom(); - if (!call) { - return; - } - const newState = !call.isLocalVideoMuted(); - call.setLocalVideoMuted(newState); - this.forceUpdate(); // TODO: just update the voip buttons - }; - private onStatusBarVisible = () => { if (this.unmounted) return; this.setState({ @@ -1640,24 +1613,6 @@ export default class RoomView extends React.Component { }); }; - /** - * called by the parent component when PageUp/Down/etc is pressed. - * - * We pass it down to the scroll panel. - */ - private handleScrollKey = ev => { - let panel; - if (this.searchResultsPanel.current) { - panel = this.searchResultsPanel.current; - } else if (this.messagePanel) { - panel = this.messagePanel; - } - - if (panel) { - panel.handleScrollKey(ev); - } - }; - /** * get any current call for this room */ From 8d072cedff41f77a89289e58435fe270408a18fe Mon Sep 17 00:00:00 2001 From: Nique Woodhouse Date: Wed, 19 May 2021 12:11:24 +0100 Subject: [PATCH 007/159] Update _MessageActionBar.scss Correct error on css hover for message action button background colour change --- res/css/views/messages/_MessageActionBar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/css/views/messages/_MessageActionBar.scss b/res/css/views/messages/_MessageActionBar.scss index 81be6d1917..e2fafe6c62 100644 --- a/res/css/views/messages/_MessageActionBar.scss +++ b/res/css/views/messages/_MessageActionBar.scss @@ -80,7 +80,7 @@ limitations under the License. background-color: $secondary-fg-color; } -.mx_MessageActionBar_maskButton:hover:after { +.mx_MessageActionBar_maskButton:hover::after { background-color: $primary-fg-color; } From 7d0f3b0d99656361484b601999d2ae8ab35905f7 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 19 May 2021 14:33:21 +0100 Subject: [PATCH 008/159] Upgrade matrix-js-sdk to 11.1.0-rc.1 --- package.json | 2 +- yarn.lock | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index f8d94e2d21..d7670f55ce 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", + "matrix-js-sdk": "11.1.0-rc.1", "matrix-widget-api": "^0.1.0-beta.14", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index 19c0646d32..0472555dd9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5669,9 +5669,10 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": - version "11.0.0" - resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/52a893a8116d60bb76f1b015d3161a15404b3628" +matrix-js-sdk@11.1.0-rc.1: + version "11.1.0-rc.1" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-11.1.0-rc.1.tgz#98da580fa51bc8c70c57984e79dc63c617a671d1" + integrity sha512-yyZeL1mHttw+EnZcGfMH+wd33s0+ZKB+KyXHA3QiqiVRWLgANjIY9QCNjbJYa9FK8zZRqO2yHiFLtTAAU379Ag== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From cde23ab6e8e3571bfc1b82f64dd20888816db973 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 19 May 2021 14:40:36 +0100 Subject: [PATCH 009/159] Prepare changelog for v3.22.0-rc.1 --- CHANGELOG.md | 105 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2582668ef9..3b4ec8b457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,108 @@ +Changes in [3.22.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.22.0-rc.1) (2021-05-19) +=============================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.21.0...v3.22.0-rc.1) + + * Upgrade to JS SDK 11.1.0-rc.1 + * Translations update from Weblate + [\#6068](https://github.com/matrix-org/matrix-react-sdk/pull/6068) + * Show DMs in space for invited members too, to match Android impl + [\#6062](https://github.com/matrix-org/matrix-react-sdk/pull/6062) + * Support filtering by alias in add existing to space dialog + [\#6057](https://github.com/matrix-org/matrix-react-sdk/pull/6057) + * Fix issue when a room without a name or alias is marked as suggested + [\#6064](https://github.com/matrix-org/matrix-react-sdk/pull/6064) + * Fix space room hierarchy not updating when removing a room + [\#6055](https://github.com/matrix-org/matrix-react-sdk/pull/6055) + * Revert "Try putting room list handling behind a lock" + [\#6060](https://github.com/matrix-org/matrix-react-sdk/pull/6060) + * Stop assuming encrypted messages are decrypted ahead of time + [\#6052](https://github.com/matrix-org/matrix-react-sdk/pull/6052) + * Add error detail when languges fail to load + [\#6059](https://github.com/matrix-org/matrix-react-sdk/pull/6059) + * Add space invaders chat effect + [\#6053](https://github.com/matrix-org/matrix-react-sdk/pull/6053) + * Create SpaceProvider and hide Spaces from the RoomProvider autocompleter + [\#6051](https://github.com/matrix-org/matrix-react-sdk/pull/6051) + * Don't mark a room as unread when redacted event is present + [\#6049](https://github.com/matrix-org/matrix-react-sdk/pull/6049) + * Add support for MSC2873: Client information for Widgets + [\#6023](https://github.com/matrix-org/matrix-react-sdk/pull/6023) + * Support UI for MSC2762: Widgets reading events from rooms + [\#5960](https://github.com/matrix-org/matrix-react-sdk/pull/5960) + * Fix crash on opening notification panel + [\#6047](https://github.com/matrix-org/matrix-react-sdk/pull/6047) + * Remove custom LoggedInView::shouldComponentUpdate logic + [\#6046](https://github.com/matrix-org/matrix-react-sdk/pull/6046) + * Fix edge cases with the new add reactions prompt button + [\#6045](https://github.com/matrix-org/matrix-react-sdk/pull/6045) + * Add ids to homeserver and passphrase fields + [\#6043](https://github.com/matrix-org/matrix-react-sdk/pull/6043) + * Update space order field validity requirements to match msc update + [\#6042](https://github.com/matrix-org/matrix-react-sdk/pull/6042) + * Try putting room list handling behind a lock + [\#6024](https://github.com/matrix-org/matrix-react-sdk/pull/6024) + * Improve progress bar progression for smaller voice messages + [\#6035](https://github.com/matrix-org/matrix-react-sdk/pull/6035) + * Fix share space edge case where space is public but not invitable + [\#6039](https://github.com/matrix-org/matrix-react-sdk/pull/6039) + * Add missing 'rel' to image view download button + [\#6033](https://github.com/matrix-org/matrix-react-sdk/pull/6033) + * Improve visible waveform for voice messages + [\#6034](https://github.com/matrix-org/matrix-react-sdk/pull/6034) + * Fix roving tab index intercepting home/end in space create menu + [\#6040](https://github.com/matrix-org/matrix-react-sdk/pull/6040) + * Decorate room avatars with publicity in add existing to space flow + [\#6030](https://github.com/matrix-org/matrix-react-sdk/pull/6030) + * Improve Spaces "Just Me" wizard + [\#6025](https://github.com/matrix-org/matrix-react-sdk/pull/6025) + * Increase hover feedback on room sub list buttons + [\#6037](https://github.com/matrix-org/matrix-react-sdk/pull/6037) + * Show alternative button during space creation wizard if no rooms + [\#6029](https://github.com/matrix-org/matrix-react-sdk/pull/6029) + * Swap rotation buttons in the image viewer + [\#6032](https://github.com/matrix-org/matrix-react-sdk/pull/6032) + * Typo: initilisation -> initialisation + [\#5915](https://github.com/matrix-org/matrix-react-sdk/pull/5915) + * Save edited state of a message when switching rooms + [\#6001](https://github.com/matrix-org/matrix-react-sdk/pull/6001) + * Fix shield icon in Untrusted Device Dialog + [\#6022](https://github.com/matrix-org/matrix-react-sdk/pull/6022) + * Do not eagerly decrypt breadcrumb rooms + [\#6028](https://github.com/matrix-org/matrix-react-sdk/pull/6028) + * Update spaces.png + [\#6031](https://github.com/matrix-org/matrix-react-sdk/pull/6031) + * Encourage more diverse reactions to content + [\#6027](https://github.com/matrix-org/matrix-react-sdk/pull/6027) + * Wrap decodeURIComponent in try-catch to protect against malformed URIs + [\#6026](https://github.com/matrix-org/matrix-react-sdk/pull/6026) + * Iterate beta feedback dialog + [\#6021](https://github.com/matrix-org/matrix-react-sdk/pull/6021) + * Disable space fields whilst their form is busy + [\#6020](https://github.com/matrix-org/matrix-react-sdk/pull/6020) + * Add missing space on beta feedback dialog + [\#6018](https://github.com/matrix-org/matrix-react-sdk/pull/6018) + * Fix colours used for the back button in space create menu + [\#6017](https://github.com/matrix-org/matrix-react-sdk/pull/6017) + * Prioritise and reduce the amount of events decrypted on application startup + [\#5980](https://github.com/matrix-org/matrix-react-sdk/pull/5980) + * Linkify topics in space room directory results + [\#6015](https://github.com/matrix-org/matrix-react-sdk/pull/6015) + * Persistent space collapsed states + [\#5972](https://github.com/matrix-org/matrix-react-sdk/pull/5972) + * Catch another instance of unlabeled avatars. + [\#6010](https://github.com/matrix-org/matrix-react-sdk/pull/6010) + * Rescale and smooth voice message playback waveform to better match + expectation + [\#5996](https://github.com/matrix-org/matrix-react-sdk/pull/5996) + * Scale voice message clock with user's font size + [\#5993](https://github.com/matrix-org/matrix-react-sdk/pull/5993) + * Remove "in development" flag from voice messages + [\#5995](https://github.com/matrix-org/matrix-react-sdk/pull/5995) + * Support voice messages on Safari + [\#5989](https://github.com/matrix-org/matrix-react-sdk/pull/5989) + * Translations update from Weblate + [\#6011](https://github.com/matrix-org/matrix-react-sdk/pull/6011) + Changes in [3.21.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.21.0) (2021-05-17) ===================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.21.0-rc.1...v3.21.0) From 10bc96dc374ec9093705a480a78f0feb06d0fb23 Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Wed, 19 May 2021 14:40:37 +0100 Subject: [PATCH 010/159] v3.22.0-rc.1 --- package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index d7670f55ce..8ecebc4eb3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.21.0", + "version": "3.22.0-rc.1", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { @@ -25,7 +25,7 @@ "bin": { "reskindex": "scripts/reskindex.js" }, - "main": "./src/index.js", + "main": "./lib/index.js", "matrix_src_main": "./src/index.js", "matrix_lib_main": "./lib/index.js", "matrix_lib_typings": "./lib/index.d.ts", @@ -200,5 +200,6 @@ "coverageReporters": [ "text" ] - } + }, + "typings": "./lib/index.d.ts" } From 21c1179f8d2726a909c5540f8e25a92fa0070dc3 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Thu, 20 May 2021 17:54:42 +0100 Subject: [PATCH 011/159] Update extensions for more files with types This migrates the another bucket of files using some amount of Flow typing to mark them as TypeScript instead. The remaining type errors are fixed in subsequent commits. --- ...eAuthEntryComponents.js => InteractiveAuthEntryComponents.tsx} | 0 .../views/dialogs/{DevtoolsDialog.js => DevtoolsDialog.tsx} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/components/views/auth/{InteractiveAuthEntryComponents.js => InteractiveAuthEntryComponents.tsx} (100%) rename src/components/views/dialogs/{DevtoolsDialog.js => DevtoolsDialog.tsx} (100%) diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.js b/src/components/views/auth/InteractiveAuthEntryComponents.tsx similarity index 100% rename from src/components/views/auth/InteractiveAuthEntryComponents.js rename to src/components/views/auth/InteractiveAuthEntryComponents.tsx diff --git a/src/components/views/dialogs/DevtoolsDialog.js b/src/components/views/dialogs/DevtoolsDialog.tsx similarity index 100% rename from src/components/views/dialogs/DevtoolsDialog.js rename to src/components/views/dialogs/DevtoolsDialog.tsx From 6574ca98fa098e3690391cfc0152ccaca2ea4cfd Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 12 May 2021 14:06:10 +0100 Subject: [PATCH 012/159] Fix basic lint errors --- .../auth/InteractiveAuthEntryComponents.tsx | 4 +- .../views/dialogs/DevtoolsDialog.tsx | 57 +++++++++++++++---- 2 files changed, 49 insertions(+), 12 deletions(-) diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx index e34349c474..5a492b14ee 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx +++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx @@ -786,7 +786,9 @@ export class FallbackAuthEntry extends React.Component { } return ( ); diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 8a035263cc..1d544af315 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -169,8 +169,16 @@ export class SendCustomEvent extends GenericEditor { { !this.state.message && } { showTglFlip &&
- -
} ; @@ -253,8 +261,17 @@ class SendAccountData extends GenericEditor { { !this.state.message && } { !this.state.message &&
- -
} ; @@ -581,8 +598,16 @@ class AccountDataExplorer extends React.PureComponent {
{ !this.state.message &&
- -
}
; @@ -1062,27 +1087,37 @@ class SettingsExplorer extends React.Component {
{_t("Value:")}  - {this.renderSettingValue(SettingsStore.getValue(this.state.viewSetting))} + {this.renderSettingValue( + SettingsStore.getValue(this.state.viewSetting), + )}
{_t("Value in this room:")}  - {this.renderSettingValue(SettingsStore.getValue(this.state.viewSetting, room.roomId))} + {this.renderSettingValue( + SettingsStore.getValue(this.state.viewSetting, room.roomId), + )}
{_t("Values at explicit levels:")} -
{this.renderExplicitSettingValues(this.state.viewSetting, null)}
+
{this.renderExplicitSettingValues(
+                                this.state.viewSetting, null,
+                            )}
{_t("Values at explicit levels in this room:")} -
{this.renderExplicitSettingValues(this.state.viewSetting, room.roomId)}
+
{this.renderExplicitSettingValues(
+                                this.state.viewSetting, room.roomId,
+                            )}
- +
From df09bdf823e3c3cc018827c93f95ebf73e58a288 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 12 May 2021 19:28:22 +0100 Subject: [PATCH 013/159] Add types to InteractiveAuthEntryComponents --- src/Terms.ts | 14 +- .../auth/InteractiveAuthEntryComponents.tsx | 371 ++++++++++-------- src/languageHandler.tsx | 8 +- 3 files changed, 213 insertions(+), 180 deletions(-) diff --git a/src/Terms.ts b/src/Terms.ts index 1bdff36cbc..1b1c152fdd 100644 --- a/src/Terms.ts +++ b/src/Terms.ts @@ -36,14 +36,18 @@ export class Service { } } -interface Policy { +export interface LocalisedPolicy { + name: string; + url: string; +} + +export interface Policy { // @ts-ignore: No great way to express indexed types together with other keys version: string; - [lang: string]: { - url: string; - }; + [lang: string]: LocalisedPolicy; } -type Policies = { + +export type Policies = { [policy: string]: Policy, }; diff --git a/src/components/views/auth/InteractiveAuthEntryComponents.tsx b/src/components/views/auth/InteractiveAuthEntryComponents.tsx index 5a492b14ee..066c064cc1 100644 --- a/src/components/views/auth/InteractiveAuthEntryComponents.tsx +++ b/src/components/views/auth/InteractiveAuthEntryComponents.tsx @@ -1,7 +1,5 @@ /* -Copyright 2016 OpenMarket Ltd -Copyright 2017 Vector Creations Ltd -Copyright 2019, 2020 The Matrix.org Foundation C.I.C. +Copyright 2016-2021 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. @@ -16,9 +14,9 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {createRef} from 'react'; -import PropTypes from 'prop-types'; -import classnames from 'classnames'; +import React, { ChangeEvent, createRef, FormEvent, MouseEvent } from 'react'; +import classNames from 'classnames'; +import { MatrixClient } from "matrix-js-sdk/src/client"; import * as sdk from '../../../index'; import { _t } from '../../../languageHandler'; @@ -27,6 +25,7 @@ import AccessibleButton from "../elements/AccessibleButton"; import Spinner from "../elements/Spinner"; import CountlyAnalytics from "../../../CountlyAnalytics"; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { LocalisedPolicy, Policies } from '../../../Terms'; /* This file contains a collection of components which are used by the * InteractiveAuth to prompt the user to enter the information needed @@ -74,21 +73,49 @@ import {replaceableComponent} from "../../../utils/replaceableComponent"; * focus: set the input focus appropriately in the form. */ +enum AuthType { + Password = "m.login.password", + Recaptcha = "m.login.recaptcha", + Terms = "m.login.terms", + Email = "m.login.email.identity", + Msisdn = "m.login.msisdn", + Sso = "m.login.sso", + SsoUnstable = "org.matrix.login.sso", +} + +/* eslint-disable camelcase */ +interface IAuthDict { + type?: AuthType; + // TODO: Remove `user` once servers support proper UIA + // See https://github.com/vector-im/element-web/issues/10312 + user?: string; + identifier?: object; + password?: string; + response?: string; + // TODO: Remove `threepid_creds` once servers support proper UIA + // See https://github.com/vector-im/element-web/issues/10312 + // See https://github.com/matrix-org/matrix-doc/issues/2220 + threepid_creds?: object; + threepidCreds?: object; +} +/* eslint-enable camelcase */ + export const DEFAULT_PHASE = 0; -@replaceableComponent("views.auth.PasswordAuthEntry") -export class PasswordAuthEntry extends React.Component { - static LOGIN_TYPE = "m.login.password"; +interface IAuthEntryProps { + matrixClient: MatrixClient; + loginType: string; + authSessionId: string; + submitAuthDict: (auth: IAuthDict) => void; + errorText?: string; + // Is the auth logic currently waiting for something to happen? + busy?: boolean; + onPhaseChange: (phase: number) => void; +} - static propTypes = { - matrixClient: PropTypes.object.isRequired, - submitAuthDict: PropTypes.func.isRequired, - errorText: PropTypes.string, - // is the auth logic currently waiting for something to - // happen? - busy: PropTypes.bool, - onPhaseChange: PropTypes.func.isRequired, - }; +@replaceableComponent("views.auth.PasswordAuthEntry") +export class PasswordAuthEntry extends React.Component { + static LOGIN_TYPE = AuthType.Password; componentDidMount() { this.props.onPhaseChange(DEFAULT_PHASE); @@ -98,12 +125,12 @@ export class PasswordAuthEntry extends React.Component { password: "", }; - _onSubmit = e => { + private onSubmit = (e: FormEvent) => { e.preventDefault(); if (this.props.busy) return; this.props.submitAuthDict({ - type: PasswordAuthEntry.LOGIN_TYPE, + type: AuthType.Password, // TODO: Remove `user` once servers support proper UIA // See https://github.com/vector-im/element-web/issues/10312 user: this.props.matrixClient.credentials.userId, @@ -115,7 +142,7 @@ export class PasswordAuthEntry extends React.Component { }); }; - _onPasswordFieldChange = ev => { + private onPasswordFieldChange = (ev: ChangeEvent) => { // enable the submit button iff the password is non-empty this.setState({ password: ev.target.value, @@ -123,7 +150,7 @@ export class PasswordAuthEntry extends React.Component { }; render() { - const passwordBoxClass = classnames({ + const passwordBoxClass = classNames({ "error": this.props.errorText, }); @@ -155,7 +182,7 @@ export class PasswordAuthEntry extends React.Component { return (

{ _t("Confirm your identity by entering your account password below.") }

-
+
{ submitButtonOrSpinner } @@ -175,26 +202,26 @@ export class PasswordAuthEntry extends React.Component { } } -@replaceableComponent("views.auth.RecaptchaAuthEntry") -export class RecaptchaAuthEntry extends React.Component { - static LOGIN_TYPE = "m.login.recaptcha"; - - static propTypes = { - submitAuthDict: PropTypes.func.isRequired, - stageParams: PropTypes.object.isRequired, - errorText: PropTypes.string, - busy: PropTypes.bool, - onPhaseChange: PropTypes.func.isRequired, +/* eslint-disable camelcase */ +interface IRecaptchaAuthEntryProps extends IAuthEntryProps { + stageParams?: { + public_key?: string; }; +} +/* eslint-enable camelcase */ + +@replaceableComponent("views.auth.RecaptchaAuthEntry") +export class RecaptchaAuthEntry extends React.Component { + static LOGIN_TYPE = AuthType.Recaptcha; componentDidMount() { this.props.onPhaseChange(DEFAULT_PHASE); } - _onCaptchaResponse = response => { + private onCaptchaResponse = (response: string) => { CountlyAnalytics.instance.track("onboarding_grecaptcha_submit"); this.props.submitAuthDict({ - type: RecaptchaAuthEntry.LOGIN_TYPE, + type: AuthType.Recaptcha, response: response, }); }; @@ -230,7 +257,7 @@ export class RecaptchaAuthEntry extends React.Component { return (
{ errorSection }
@@ -238,18 +265,28 @@ export class RecaptchaAuthEntry extends React.Component { } } -@replaceableComponent("views.auth.TermsAuthEntry") -export class TermsAuthEntry extends React.Component { - static LOGIN_TYPE = "m.login.terms"; - - static propTypes = { - submitAuthDict: PropTypes.func.isRequired, - stageParams: PropTypes.object.isRequired, - errorText: PropTypes.string, - busy: PropTypes.bool, - showContinue: PropTypes.bool, - onPhaseChange: PropTypes.func.isRequired, +interface ITermsAuthEntryProps extends IAuthEntryProps { + stageParams?: { + policies?: Policies; }; + showContinue: boolean; +} + +interface LocalisedPolicyWithId extends LocalisedPolicy { + id: string; +} + +interface ITermsAuthEntryState { + policies: LocalisedPolicyWithId[]; + toggledPolicies: { + [policy: string]: boolean; + }; + errorText?: string; +} + +@replaceableComponent("views.auth.TermsAuthEntry") +export class TermsAuthEntry extends React.Component { + static LOGIN_TYPE = AuthType.Terms; constructor(props) { super(props); @@ -294,8 +331,11 @@ export class TermsAuthEntry extends React.Component { initToggles[policyId] = false; - langPolicy.id = policyId; - pickedPolicies.push(langPolicy); + pickedPolicies.push({ + id: policyId, + name: langPolicy.name, + url: langPolicy.url, + }); } this.state = { @@ -312,10 +352,10 @@ export class TermsAuthEntry extends React.Component { } tryContinue = () => { - this._trySubmit(); + this.trySubmit(); }; - _togglePolicy(policyId) { + private togglePolicy(policyId: string) { const newToggles = {}; for (const policy of this.state.policies) { let checked = this.state.toggledPolicies[policy.id]; @@ -326,7 +366,7 @@ export class TermsAuthEntry extends React.Component { this.setState({"toggledPolicies": newToggles}); } - _trySubmit = () => { + private trySubmit = () => { let allChecked = true; for (const policy of this.state.policies) { const checked = this.state.toggledPolicies[policy.id]; @@ -334,7 +374,7 @@ export class TermsAuthEntry extends React.Component { } if (allChecked) { - this.props.submitAuthDict({type: TermsAuthEntry.LOGIN_TYPE}); + this.props.submitAuthDict({type: AuthType.Terms}); CountlyAnalytics.instance.track("onboarding_terms_complete"); } else { this.setState({errorText: _t("Please review and accept all of the homeserver's policies")}); @@ -356,7 +396,7 @@ export class TermsAuthEntry extends React.Component { checkboxes.push( // XXX: replace with StyledCheckbox , ); @@ -375,7 +415,7 @@ export class TermsAuthEntry extends React.Component { if (this.props.showContinue !== false) { // XXX: button classes submitButton = ; + onClick={this.trySubmit} disabled={!allChecked}>{_t("Accept")}; } return ( @@ -389,21 +429,18 @@ export class TermsAuthEntry extends React.Component { } } -@replaceableComponent("views.auth.EmailIdentityAuthEntry") -export class EmailIdentityAuthEntry extends React.Component { - static LOGIN_TYPE = "m.login.email.identity"; - - static propTypes = { - matrixClient: PropTypes.object.isRequired, - submitAuthDict: PropTypes.func.isRequired, - authSessionId: PropTypes.string.isRequired, - clientSecret: PropTypes.string.isRequired, - inputs: PropTypes.object.isRequired, - stageState: PropTypes.object.isRequired, - fail: PropTypes.func.isRequired, - setEmailSid: PropTypes.func.isRequired, - onPhaseChange: PropTypes.func.isRequired, +interface IEmailIdentityAuthEntryProps extends IAuthEntryProps { + inputs?: { + emailAddress?: string; }; + stageState?: { + emailSid: string; + }; +} + +@replaceableComponent("views.auth.EmailIdentityAuthEntry") +export class EmailIdentityAuthEntry extends React.Component { + static LOGIN_TYPE = AuthType.Email; componentDidMount() { this.props.onPhaseChange(DEFAULT_PHASE); @@ -427,7 +464,7 @@ export class EmailIdentityAuthEntry extends React.Component { return (

{ _t("A confirmation email has been sent to %(emailAddress)s", - { emailAddress: (sub) => { this.props.inputs.emailAddress } }, + { emailAddress: { this.props.inputs.emailAddress } }, ) }

{ _t("Open the link in the email to continue registration.") }

@@ -437,37 +474,34 @@ export class EmailIdentityAuthEntry extends React.Component { } } -@replaceableComponent("views.auth.MsisdnAuthEntry") -export class MsisdnAuthEntry extends React.Component { - static LOGIN_TYPE = "m.login.msisdn"; - - static propTypes = { - inputs: PropTypes.shape({ - phoneCountry: PropTypes.string, - phoneNumber: PropTypes.string, - }), - fail: PropTypes.func, - clientSecret: PropTypes.func, - submitAuthDict: PropTypes.func.isRequired, - matrixClient: PropTypes.object, - onPhaseChange: PropTypes.func.isRequired, +interface IMsisdnAuthEntryProps extends IAuthEntryProps { + inputs: { + phoneCountry: string; + phoneNumber: string; }; + clientSecret: string; + fail: (error: Error) => void; +} + +@replaceableComponent("views.auth.MsisdnAuthEntry") +export class MsisdnAuthEntry extends React.Component { + static LOGIN_TYPE = AuthType.Msisdn; + + private submitUrl: string; + private sid: string; + private msisdn: string; state = { token: '', requestingToken: false, + errorText: '', }; componentDidMount() { this.props.onPhaseChange(DEFAULT_PHASE); - this._submitUrl = null; - this._sid = null; - this._msisdn = null; - this._tokenBox = null; - this.setState({requestingToken: true}); - this._requestMsisdnToken().catch((e) => { + this.requestMsisdnToken().catch((e) => { this.props.fail(e); }).finally(() => { this.setState({requestingToken: false}); @@ -477,26 +511,26 @@ export class MsisdnAuthEntry extends React.Component { /* * Requests a verification token by SMS. */ - _requestMsisdnToken() { + private requestMsisdnToken(): Promise { return this.props.matrixClient.requestRegisterMsisdnToken( this.props.inputs.phoneCountry, this.props.inputs.phoneNumber, this.props.clientSecret, 1, // TODO: Multiple send attempts? ).then((result) => { - this._submitUrl = result.submit_url; - this._sid = result.sid; - this._msisdn = result.msisdn; + this.submitUrl = result.submit_url; + this.sid = result.sid; + this.msisdn = result.msisdn; }); } - _onTokenChange = e => { + private onTokenChange = (e: ChangeEvent) => { this.setState({ token: e.target.value, }); }; - _onFormSubmit = async e => { + private onFormSubmit = async (e: FormEvent) => { e.preventDefault(); if (this.state.token == '') return; @@ -506,20 +540,20 @@ export class MsisdnAuthEntry extends React.Component { try { let result; - if (this._submitUrl) { + if (this.submitUrl) { result = await this.props.matrixClient.submitMsisdnTokenOtherUrl( - this._submitUrl, this._sid, this.props.clientSecret, this.state.token, + this.submitUrl, this.sid, this.props.clientSecret, this.state.token, ); } else { throw new Error("The registration with MSISDN flow is misconfigured"); } if (result.success) { const creds = { - sid: this._sid, + sid: this.sid, client_secret: this.props.clientSecret, }; this.props.submitAuthDict({ - type: MsisdnAuthEntry.LOGIN_TYPE, + type: AuthType.Msisdn, // TODO: Remove `threepid_creds` once servers support proper UIA // See https://github.com/vector-im/element-web/issues/10312 // See https://github.com/matrix-org/matrix-doc/issues/2220 @@ -543,7 +577,7 @@ export class MsisdnAuthEntry extends React.Component { return ; } else { const enableSubmit = Boolean(this.state.token); - const submitClasses = classnames({ + const submitClasses = classNames({ mx_InteractiveAuthEntryComponents_msisdnSubmit: true, mx_GeneralButton: true, }); @@ -558,16 +592,16 @@ export class MsisdnAuthEntry extends React.Component { return (

{ _t("A text message has been sent to %(msisdn)s", - { msisdn: { this._msisdn } }, + { msisdn: { this.msisdn } }, ) }

{ _t("Please enter the code it contains:") }

- +
@@ -584,40 +618,40 @@ export class MsisdnAuthEntry extends React.Component { } } -@replaceableComponent("views.auth.SSOAuthEntry") -export class SSOAuthEntry extends React.Component { - static propTypes = { - matrixClient: PropTypes.object.isRequired, - authSessionId: PropTypes.string.isRequired, - loginType: PropTypes.string.isRequired, - submitAuthDict: PropTypes.func.isRequired, - errorText: PropTypes.string, - onPhaseChange: PropTypes.func.isRequired, - continueText: PropTypes.string, - continueKind: PropTypes.string, - onCancel: PropTypes.func, - }; +interface ISSOAuthEntryProps extends IAuthEntryProps { + continueText?: string; + continueKind?: string; + onCancel?: () => void; +} - static LOGIN_TYPE = "m.login.sso"; - static UNSTABLE_LOGIN_TYPE = "org.matrix.login.sso"; +interface ISSOAuthEntryState { + phase: number; + attemptFailed: boolean; +} + +@replaceableComponent("views.auth.SSOAuthEntry") +export class SSOAuthEntry extends React.Component { + static LOGIN_TYPE = AuthType.Sso; + static UNSTABLE_LOGIN_TYPE = AuthType.SsoUnstable; static PHASE_PREAUTH = 1; // button to start SSO static PHASE_POSTAUTH = 2; // button to confirm SSO completed - _ssoUrl: string; + private ssoUrl: string; + private popupWindow: Window; constructor(props) { super(props); // We actually send the user through fallback auth so we don't have to // deal with a redirect back to us, losing application context. - this._ssoUrl = props.matrixClient.getFallbackAuthUrl( + this.ssoUrl = props.matrixClient.getFallbackAuthUrl( this.props.loginType, this.props.authSessionId, ); - this._popupWindow = null; - window.addEventListener("message", this._onReceiveMessage); + this.popupWindow = null; + window.addEventListener("message", this.onReceiveMessage); this.state = { phase: SSOAuthEntry.PHASE_PREAUTH, @@ -625,15 +659,15 @@ export class SSOAuthEntry extends React.Component { }; } - componentDidMount(): void { + componentDidMount() { this.props.onPhaseChange(SSOAuthEntry.PHASE_PREAUTH); } componentWillUnmount() { - window.removeEventListener("message", this._onReceiveMessage); - if (this._popupWindow) { - this._popupWindow.close(); - this._popupWindow = null; + window.removeEventListener("message", this.onReceiveMessage); + if (this.popupWindow) { + this.popupWindow.close(); + this.popupWindow = null; } } @@ -643,11 +677,11 @@ export class SSOAuthEntry extends React.Component { }); }; - _onReceiveMessage = event => { + private onReceiveMessage = (event: MessageEvent) => { if (event.data === "authDone" && event.origin === this.props.matrixClient.getHomeserverUrl()) { - if (this._popupWindow) { - this._popupWindow.close(); - this._popupWindow = null; + if (this.popupWindow) { + this.popupWindow.close(); + this.popupWindow = null; } } }; @@ -657,7 +691,7 @@ export class SSOAuthEntry extends React.Component { // certainly will need to open the thing in a new tab to avoid losing application // context. - this._popupWindow = window.open(this._ssoUrl, "_blank"); + this.popupWindow = window.open(this.ssoUrl, "_blank"); this.setState({phase: SSOAuthEntry.PHASE_POSTAUTH}); this.props.onPhaseChange(SSOAuthEntry.PHASE_POSTAUTH); }; @@ -716,46 +750,37 @@ export class SSOAuthEntry extends React.Component { } @replaceableComponent("views.auth.FallbackAuthEntry") -export class FallbackAuthEntry extends React.Component { - static propTypes = { - matrixClient: PropTypes.object.isRequired, - authSessionId: PropTypes.string.isRequired, - loginType: PropTypes.string.isRequired, - submitAuthDict: PropTypes.func.isRequired, - errorText: PropTypes.string, - onPhaseChange: PropTypes.func.isRequired, - }; +export class FallbackAuthEntry extends React.Component { + private popupWindow: Window; + private fallbackButton = createRef(); constructor(props) { super(props); // we have to make the user click a button, as browsers will block // the popup if we open it immediately. - this._popupWindow = null; - window.addEventListener("message", this._onReceiveMessage); - - this._fallbackButton = createRef(); + this.popupWindow = null; + window.addEventListener("message", this.onReceiveMessage); } - componentDidMount() { this.props.onPhaseChange(DEFAULT_PHASE); } componentWillUnmount() { - window.removeEventListener("message", this._onReceiveMessage); - if (this._popupWindow) { - this._popupWindow.close(); + window.removeEventListener("message", this.onReceiveMessage); + if (this.popupWindow) { + this.popupWindow.close(); } } focus = () => { - if (this._fallbackButton.current) { - this._fallbackButton.current.focus(); + if (this.fallbackButton.current) { + this.fallbackButton.current.focus(); } }; - _onShowFallbackClick = e => { + private onShowFallbackClick = (e: MouseEvent) => { e.preventDefault(); e.stopPropagation(); @@ -763,10 +788,10 @@ export class FallbackAuthEntry extends React.Component { this.props.loginType, this.props.authSessionId, ); - this._popupWindow = window.open(url, "_blank"); + this.popupWindow = window.open(url, "_blank"); }; - _onReceiveMessage = event => { + private onReceiveMessage = (event: MessageEvent) => { if ( event.data === "authDone" && event.origin === this.props.matrixClient.getHomeserverUrl() @@ -786,7 +811,7 @@ export class FallbackAuthEntry extends React.Component { } return (
- { + { _t("Start authentication") } {errorSection} @@ -795,20 +820,22 @@ export class FallbackAuthEntry extends React.Component { } } -const AuthEntryComponents = [ - PasswordAuthEntry, - RecaptchaAuthEntry, - EmailIdentityAuthEntry, - MsisdnAuthEntry, - TermsAuthEntry, - SSOAuthEntry, -]; - -export default function getEntryComponentForLoginType(loginType) { - for (const c of AuthEntryComponents) { - if (c.LOGIN_TYPE === loginType || c.UNSTABLE_LOGIN_TYPE === loginType) { - return c; - } +export default function getEntryComponentForLoginType(loginType: AuthType): typeof React.Component { + switch (loginType) { + case AuthType.Password: + return PasswordAuthEntry; + case AuthType.Recaptcha: + return RecaptchaAuthEntry; + case AuthType.Email: + return EmailIdentityAuthEntry; + case AuthType.Msisdn: + return MsisdnAuthEntry; + case AuthType.Terms: + return TermsAuthEntry; + case AuthType.Sso: + case AuthType.SsoUnstable: + return SSOAuthEntry; + default: + return FallbackAuthEntry; } - return FallbackAuthEntry; } diff --git a/src/languageHandler.tsx b/src/languageHandler.tsx index 26c89afec6..16950dc008 100644 --- a/src/languageHandler.tsx +++ b/src/languageHandler.tsx @@ -105,12 +105,14 @@ function safeCounterpartTranslate(text: string, options?: object) { return translated; } +type SubstitutionValue = number | string | React.ReactNode | ((sub: string) => React.ReactNode); + export interface IVariables { count?: number; - [key: string]: number | string; + [key: string]: SubstitutionValue; } -type Tags = Record React.ReactNode>; +type Tags = Record; export type TranslatedString = string | React.ReactNode; @@ -247,7 +249,7 @@ export function replaceByRegexes(text: string, mapping: IVariables | Tags): stri let replaced; // If substitution is a function, call it if (mapping[regexpString] instanceof Function) { - replaced = (mapping as Tags)[regexpString].apply(null, capturedGroups); + replaced = ((mapping as Tags)[regexpString] as Function)(...capturedGroups); } else { replaced = mapping[regexpString]; } From d9e490926b5bd4f0601f826a6918c954d41791d8 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 18 May 2021 15:20:08 +0100 Subject: [PATCH 014/159] Add types to DevtoolsDialog --- .../views/dialogs/DevtoolsDialog.tsx | 392 ++++++++++-------- 1 file changed, 221 insertions(+), 171 deletions(-) diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 1d544af315..81d3a77327 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -1,5 +1,6 @@ /* Copyright 2017 Michael Telatynski <7t3chguy@gmail.com> +Copyright 2018-2021 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. @@ -14,8 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {useState, useEffect} from 'react'; -import PropTypes from 'prop-types'; +import React, {useState, useEffect, ChangeEvent, MouseEvent} from 'react'; import * as sdk from '../../../index'; import SyntaxHighlight from '../elements/SyntaxHighlight'; import { _t } from '../../../languageHandler'; @@ -30,8 +30,9 @@ import { PHASE_DONE, PHASE_STARTED, PHASE_CANCELLED, + VerificationRequest, } from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest"; -import WidgetStore from "../../../stores/WidgetStore"; +import WidgetStore, { IApp } from "../../../stores/WidgetStore"; import {UPDATE_EVENT} from "../../../stores/AsyncStore"; import {SETTINGS} from "../../../settings/Settings"; import SettingsStore, {LEVEL_ORDER} from "../../../settings/SettingsStore"; @@ -40,17 +41,22 @@ import ErrorDialog from "./ErrorDialog"; import {replaceableComponent} from "../../../utils/replaceableComponent"; import {Room} from "matrix-js-sdk/src/models/room"; import {MatrixEvent} from "matrix-js-sdk/src/models/event"; +import { SettingLevel } from '../../../settings/SettingLevel'; -class GenericEditor extends React.PureComponent { - // static propTypes = {onBack: PropTypes.func.isRequired}; +interface IGenericEditorProps { + onBack: () => void; +} - constructor(props) { - super(props); - this._onChange = this._onChange.bind(this); - this.onBack = this.onBack.bind(this); - } +interface IGenericEditorState { + message?: string; + [inputId: string]: boolean | string; +} - onBack() { +abstract class GenericEditor< + P extends IGenericEditorProps = IGenericEditorProps, + S extends IGenericEditorState = IGenericEditorState, +> extends React.PureComponent { + protected onBack = () => { if (this.state.message) { this.setState({ message: null }); } else { @@ -58,47 +64,60 @@ class GenericEditor extends React.PureComponent { } } - _onChange(e) { + protected onChange = (e: ChangeEvent) => { + // @ts-ignore: Unsure how to convince TS this is okay when the state + // type can be extended. this.setState({[e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value}); } - _buttons() { - return
+ protected abstract send(); + + protected buttons(): React.ReactNode { + return
- { !this.state.message && } + { !this.state.message && }
; } - textInput(id, label) { + protected textInput(id: string, label: string): React.ReactNode { return ; } } -export class SendCustomEvent extends GenericEditor { - static getLabel() { return _t('Send Custom Event'); } - - static propTypes = { - onBack: PropTypes.func.isRequired, - room: PropTypes.instanceOf(Room).isRequired, - forceStateEvent: PropTypes.bool, - forceGeneralEvent: PropTypes.bool, - inputs: PropTypes.object, +interface ISendCustomEventProps extends IGenericEditorProps { + room: Room; + forceStateEvent?: boolean; + forceGeneralEvent?: boolean; + inputs?: { + eventType?: string; + stateKey?: string; + evContent?: string; }; +} + +interface ISendCustomEventState extends IGenericEditorState { + isStateEvent: boolean; + eventType: string; + stateKey: string; + evContent: string; +} + +export class SendCustomEvent extends GenericEditor { + static getLabel() { return _t('Send Custom Event'); } static contextType = MatrixClientContext; constructor(props) { super(props); - this._send = this._send.bind(this); const {eventType, stateKey, evContent} = Object.assign({ eventType: '', @@ -115,7 +134,7 @@ export class SendCustomEvent extends GenericEditor { }; } - send(content) { + private doSend(content: object): Promise { const cli = this.context; if (this.state.isStateEvent) { return cli.sendStateEvent(this.props.room.roomId, this.state.eventType, content, this.state.stateKey); @@ -124,7 +143,7 @@ export class SendCustomEvent extends GenericEditor { } } - async _send() { + protected send = async () => { if (this.state.eventType === '') { this.setState({ message: _t('You must specify an event type!') }); return; @@ -133,7 +152,7 @@ export class SendCustomEvent extends GenericEditor { let message; try { const content = JSON.parse(this.state.evContent); - await this.send(content); + await this.doSend(content); message = _t('Event sent!'); } catch (e) { message = _t('Failed to send custom event.') + ' (' + e.toString() + ')'; @@ -147,7 +166,7 @@ export class SendCustomEvent extends GenericEditor {
{ this.state.message }
- { this._buttons() } + { this.buttons() }
; } @@ -163,16 +182,16 @@ export class SendCustomEvent extends GenericEditor {
+ autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
-
+
- { !this.state.message && } + { !this.state.message && } { showTglFlip &&
; } @@ -255,17 +282,17 @@ class SendAccountData extends GenericEditor {
+ autoComplete="off" value={this.state.evContent} onChange={this.onChange} element="textarea" />
-
+
- { !this.state.message && } + { !this.state.message && } { !this.state.message &&
-
+
@@ -482,31 +517,29 @@ class RoomStateExplorer extends React.PureComponent {
{ list }
-
+
; } } -class AccountDataExplorer extends React.PureComponent { - static getLabel() { return _t('Explore Account Data'); } +interface IAccountDataExplorerState { + isRoomAccountData: boolean; + event?: MatrixEvent; + editing: boolean; + queryEventType: string; + [inputId: string]: boolean | string; +} - static propTypes = { - onBack: PropTypes.func.isRequired, - room: PropTypes.instanceOf(Room).isRequired, - }; +class AccountDataExplorer extends React.PureComponent { + static getLabel() { return _t('Explore Account Data'); } static contextType = MatrixClientContext; constructor(props) { super(props); - this.onBack = this.onBack.bind(this); - this.editEv = this.editEv.bind(this); - this._onChange = this._onChange.bind(this); - this.onQueryEventType = this.onQueryEventType.bind(this); - this.state = { isRoomAccountData: false, event: null, @@ -516,20 +549,20 @@ class AccountDataExplorer extends React.PureComponent { }; } - getData() { + private getData(): Record { if (this.state.isRoomAccountData) { return this.props.room.accountData; } return this.context.store.accountData; } - onViewSourceClick(event) { + private onViewSourceClick(event: MatrixEvent) { return () => { this.setState({ event }); }; } - onBack() { + private onBack = () => { if (this.state.editing) { this.setState({ editing: false }); } else if (this.state.event) { @@ -539,15 +572,15 @@ class AccountDataExplorer extends React.PureComponent { } } - _onChange(e) { + private onChange = (e: ChangeEvent) => { this.setState({[e.target.id]: e.target.type === 'checkbox' ? e.target.checked : e.target.value}); } - editEv() { + private editEv = () => { this.setState({ editing: true }); } - onQueryEventType(queryEventType) { + private onQueryEventType = (queryEventType: string) => { this.setState({ queryEventType }); } @@ -570,7 +603,7 @@ class AccountDataExplorer extends React.PureComponent { { JSON.stringify(this.state.event.event, null, 2) }
-
+
@@ -595,40 +628,41 @@ class AccountDataExplorer extends React.PureComponent { { rows }
-
+
- { !this.state.message &&
+
} +
; } } -class ServersInRoomList extends React.PureComponent { +interface IServersInRoomListState { + query: string; +} + +class ServersInRoomList extends React.PureComponent { static getLabel() { return _t('View Servers in Room'); } - static propTypes = { - onBack: PropTypes.func.isRequired, - room: PropTypes.instanceOf(Room).isRequired, - }; - static contextType = MatrixClientContext; + private servers: React.ReactElement[]; + constructor(props) { super(props); const room = this.props.room; - const servers = new Set(); + const servers: Set = new Set(); room.currentState.getStateEvents("m.room.member").forEach(ev => servers.add(ev.getSender().split(":")[1])); this.servers = Array.from(servers).map(s =>
-
+
; @@ -667,7 +701,10 @@ const PHASE_MAP = { [PHASE_CANCELLED]: "cancelled", }; -function VerificationRequest({txnId, request}) { +const VerificationRequest: React.FC<{ + txnId: string; + request: VerificationRequest; +}> = ({txnId, request}) => { const [, updateState] = useState(); const [timeout, setRequestTimeout] = useState(request.timeout); @@ -704,7 +741,7 @@ function VerificationRequest({txnId, request}) {
); } -class VerificationExplorer extends React.Component { +class VerificationExplorer extends React.PureComponent { static getLabel() { return _t("Verification Requests"); } @@ -712,7 +749,7 @@ class VerificationExplorer extends React.Component { /* Ensure this.context is the cli */ static contextType = MatrixClientContext; - onNewRequest = () => { + private onNewRequest = () => { this.forceUpdate(); } @@ -738,14 +775,19 @@ class VerificationExplorer extends React.Component { , )}
-
+
); } } -class WidgetExplorer extends React.Component { +interface IWidgetExplorerState { + query: string; + editWidget?: IApp; +} + +class WidgetExplorer extends React.Component { static getLabel() { return _t("Active Widgets"); } @@ -759,19 +801,19 @@ class WidgetExplorer extends React.Component { }; } - onWidgetStoreUpdate = () => { + private onWidgetStoreUpdate = () => { this.forceUpdate(); }; - onQueryChange = (query) => { + private onQueryChange = (query: string) => { this.setState({query}); }; - onEditWidget = (widget) => { + private onEditWidget = (widget: IApp) => { this.setState({editWidget: widget}); }; - onBack = () => { + private onBack = () => { const widgets = WidgetStore.instance.getApps(this.props.room.roomId); if (this.state.editWidget && widgets.includes(this.state.editWidget)) { this.setState({editWidget: null}); @@ -794,13 +836,16 @@ class WidgetExplorer extends React.Component { const editWidget = this.state.editWidget; const widgets = WidgetStore.instance.getApps(room.roomId); if (editWidget && widgets.includes(editWidget)) { - const allState = Array.from(Array.from(room.currentState.events.values()).map(e => e.values())) - .reduce((p, c) => {p.push(...c); return p;}, []); + const allState = Array.from( + Array.from(room.currentState.events.values()).map((e: Map) => { + return e.values(); + }), + ).reduce((p, c) => { p.push(...c); return p; }, []); const stateEv = allState.find(ev => ev.getId() === editWidget.eventId); if (!stateEv) { // "should never happen" return
{_t("There was an error finding this widget.")} -
+
; @@ -829,14 +874,22 @@ class WidgetExplorer extends React.Component { })}
-
+
); } } -class SettingsExplorer extends React.Component { +interface ISettingsExplorerState { + query: string; + editSetting?: string; + viewSetting?: string; + explicitValues?: string; + explicitRoomValues?: string; + } + +class SettingsExplorer extends React.PureComponent { static getLabel() { return _t("Settings Explorer"); } @@ -854,19 +907,19 @@ class SettingsExplorer extends React.Component { }; } - onQueryChange = (ev) => { + private onQueryChange = (ev: ChangeEvent) => { this.setState({query: ev.target.value}); }; - onExplValuesEdit = (ev) => { + private onExplValuesEdit = (ev: ChangeEvent) => { this.setState({explicitValues: ev.target.value}); }; - onExplRoomValuesEdit = (ev) => { + private onExplRoomValuesEdit = (ev: ChangeEvent) => { this.setState({explicitRoomValues: ev.target.value}); }; - onBack = () => { + private onBack = () => { if (this.state.editSetting) { this.setState({editSetting: null}); } else if (this.state.viewSetting) { @@ -876,12 +929,12 @@ class SettingsExplorer extends React.Component { } }; - onViewClick = (ev, settingId) => { + private onViewClick = (ev: MouseEvent, settingId: string) => { ev.preventDefault(); this.setState({viewSetting: settingId}); }; - onEditClick = (ev, settingId) => { + private onEditClick = (ev: MouseEvent, settingId: string) => { ev.preventDefault(); this.setState({ editSetting: settingId, @@ -890,7 +943,7 @@ class SettingsExplorer extends React.Component { }); }; - onSaveClick = async () => { + private onSaveClick = async () => { try { const settingId = this.state.editSetting; const parsedExplicit = JSON.parse(this.state.explicitValues); @@ -899,7 +952,7 @@ class SettingsExplorer extends React.Component { console.log(`[Devtools] Setting value of ${settingId} at ${level} from user input`); try { const val = parsedExplicit[level]; - await SettingsStore.setValue(settingId, null, level, val); + await SettingsStore.setValue(settingId, null, level as SettingLevel, val); } catch (e) { console.warn(e); } @@ -909,7 +962,7 @@ class SettingsExplorer extends React.Component { console.log(`[Devtools] Setting value of ${settingId} at ${level} in ${roomId} from user input`); try { const val = parsedExplicitRoom[level]; - await SettingsStore.setValue(settingId, roomId, level, val); + await SettingsStore.setValue(settingId, roomId, level as SettingLevel, val); } catch (e) { console.warn(e); } @@ -926,7 +979,7 @@ class SettingsExplorer extends React.Component { } }; - renderSettingValue(val) { + private renderSettingValue(val: any): string { // Note: we don't .toString() a string because we want JSON.stringify to inject quotes for us const toStringTypes = ['boolean', 'number']; if (toStringTypes.includes(typeof(val))) { @@ -936,7 +989,7 @@ class SettingsExplorer extends React.Component { } } - renderExplicitSettingValues(setting, roomId) { + private renderExplicitSettingValues(setting: string, roomId: string): string { const vals = {}; for (const level of LEVEL_ORDER) { try { @@ -951,7 +1004,7 @@ class SettingsExplorer extends React.Component { return JSON.stringify(vals, null, 4); } - renderCanEditLevel(roomId, level) { + private renderCanEditLevel(roomId: string, level: SettingLevel): React.ReactNode { const canEdit = SettingsStore.canSetValue(this.state.editSetting, roomId, level); const className = canEdit ? 'mx_DevTools_SettingsExplorer_mutable' : 'mx_DevTools_SettingsExplorer_immutable'; return {canEdit.toString()}; @@ -1006,7 +1059,7 @@ class SettingsExplorer extends React.Component {
-
+
@@ -1068,7 +1121,7 @@ class SettingsExplorer extends React.Component {
-
+
@@ -1114,7 +1167,7 @@ class SettingsExplorer extends React.Component {
-
+
@@ -1126,7 +1179,11 @@ class SettingsExplorer extends React.Component { } } -const Entries = [ +type DevtoolsDialogEntry = React.JSXElementConstructor & { + getLabel: () => string; +}; + +const Entries: DevtoolsDialogEntry[] = [ SendCustomEvent, RoomStateExplorer, SendAccountData, @@ -1137,43 +1194,36 @@ const Entries = [ SettingsExplorer, ]; -@replaceableComponent("views.dialogs.DevtoolsDialog") -export default class DevtoolsDialog extends React.PureComponent { - static propTypes = { - roomId: PropTypes.string.isRequired, - onFinished: PropTypes.func.isRequired, - }; +interface IProps { + roomId: string; + onFinished: (finished: boolean) => void; +} +interface IState { + mode?: DevtoolsDialogEntry; +} + +@replaceableComponent("views.dialogs.DevtoolsDialog") +export default class DevtoolsDialog extends React.PureComponent { constructor(props) { super(props); - this.onBack = this.onBack.bind(this); - this.onCancel = this.onCancel.bind(this); this.state = { mode: null, }; } - componentWillUnmount() { - this._unmounted = true; - } - - _setMode(mode) { + private setMode(mode: DevtoolsDialogEntry) { return () => { this.setState({ mode }); }; } - onBack() { - if (this.prevMode) { - this.setState({ mode: this.prevMode }); - this.prevMode = null; - } else { - this.setState({ mode: null }); - } + private onBack = () => { + this.setState({ mode: null }); } - onCancel() { + private onCancel = () => { this.props.onFinished(false); } @@ -1200,12 +1250,12 @@ export default class DevtoolsDialog extends React.PureComponent {
{ Entries.map((Entry) => { const label = Entry.getLabel(); - const onClick = this._setMode(Entry); + const onClick = this.setMode(Entry); return ; }) }
-
+
; From 431b4607a4c7489e91f4886c6f5b8d8e9beeb855 Mon Sep 17 00:00:00 2001 From: c-cal Date: Thu, 20 May 2021 15:07:41 +0000 Subject: [PATCH 015/159] Translated using Weblate (French) Currently translated at 99.7% (2966 of 2974 strings) Translation: Element Web/matrix-react-sdk Translate-URL: https://translate.element.io/projects/element-web/matrix-react-sdk/fr/ --- src/i18n/strings/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/fr.json b/src/i18n/strings/fr.json index dc8b701e35..6b2ee03773 100644 --- a/src/i18n/strings/fr.json +++ b/src/i18n/strings/fr.json @@ -936,7 +936,7 @@ "Failed to load group members": "Échec du chargement des membres du groupe", "Failed to invite users to the room:": "Échec de l’invitation d'utilisateurs dans le salon :", "There was an error joining the room": "Une erreur est survenue en rejoignant le salon", - "You do not have permission to invite people to this room.": "Vous n’avez pas la permission d’envoyer des invitations dans ce salon.", + "You do not have permission to invite people to this room.": "Vous n'avez pas la permission d'inviter des personnes dans ce salon.", "User %(user_id)s does not exist": "L’utilisateur %(user_id)s n’existe pas", "Unknown server error": "Erreur de serveur inconnue", "Show a reminder to enable Secure Message Recovery in encrypted rooms": "Afficher un rappel pour activer la récupération de messages sécurisée dans les salons chiffrés", From d0da4b2a2578688dc4892ecd68f0f6c0c9317e90 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 21 May 2021 12:37:34 +0100 Subject: [PATCH 016/159] Use separate name for verification request component --- src/components/views/dialogs/DevtoolsDialog.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 81d3a77327..c4be186da1 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -701,7 +701,7 @@ const PHASE_MAP = { [PHASE_CANCELLED]: "cancelled", }; -const VerificationRequest: React.FC<{ +const VerificationRequestExplorer: React.FC<{ txnId: string; request: VerificationRequest; }> = ({txnId, request}) => { @@ -772,7 +772,7 @@ class VerificationExplorer extends React.PureComponent { return (
{Array.from(inRoomRequests.entries()).reverse().map(([txnId, request]) => - , + , )}
From d59b2b357936d4b66595eaea2833996ab81fbd79 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 21 May 2021 12:38:32 +0100 Subject: [PATCH 017/159] Fix unintended buttons class change --- .../views/dialogs/DevtoolsDialog.tsx | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index c4be186da1..7df57b030f 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -73,7 +73,7 @@ abstract class GenericEditor< protected abstract send(); protected buttons(): React.ReactNode { - return
+ return
{ !this.state.message && }
; @@ -184,7 +184,7 @@ export class SendCustomEvent extends GenericEditor
-
+
{ !this.state.message && } { showTglFlip &&
@@ -284,7 +284,7 @@ class SendAccountData extends GenericEditor
-
+
{ !this.state.message && } { !this.state.message &&
@@ -472,7 +472,7 @@ class RoomStateExplorer extends React.PureComponent
-
+
@@ -517,7 +517,7 @@ class RoomStateExplorer extends React.PureComponent { list }
-
+
; @@ -603,7 +603,7 @@ class AccountDataExplorer extends React.PureComponent
-
+
@@ -628,7 +628,7 @@ class AccountDataExplorer extends React.PureComponent
-
+
-
+
; @@ -775,7 +775,7 @@ class VerificationExplorer extends React.PureComponent { , )}
-
+
); @@ -845,7 +845,7 @@ class WidgetExplorer extends React.Component {_t("There was an error finding this widget.")} -
+
; @@ -874,7 +874,7 @@ class WidgetExplorer extends React.Component
-
+
); @@ -1059,7 +1059,7 @@ class SettingsExplorer extends React.PureComponent
-
+
@@ -1121,7 +1121,7 @@ class SettingsExplorer extends React.PureComponent
-
+
@@ -1167,7 +1167,7 @@ class SettingsExplorer extends React.PureComponent
-
+
@@ -1255,7 +1255,7 @@ export default class DevtoolsDialog extends React.PureComponent }) }
-
+
; From f8e61a982b6399f5c2133cf31f5f8d0d01ab0611 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Fri, 21 May 2021 12:41:59 +0100 Subject: [PATCH 018/159] One less Set --- src/components/views/dialogs/DevtoolsDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/dialogs/DevtoolsDialog.tsx b/src/components/views/dialogs/DevtoolsDialog.tsx index 7df57b030f..0ea77cc9e8 100644 --- a/src/components/views/dialogs/DevtoolsDialog.tsx +++ b/src/components/views/dialogs/DevtoolsDialog.tsx @@ -662,7 +662,7 @@ class ServersInRoomList extends React.PureComponent = new Set(); + const servers = new Set(); room.currentState.getStateEvents("m.room.member").forEach(ev => servers.add(ev.getSender().split(":")[1])); this.servers = Array.from(servers).map(s =>
); } diff --git a/src/components/views/elements/TooltipButton.js b/src/components/views/elements/TooltipButton.tsx similarity index 90% rename from src/components/views/elements/TooltipButton.js rename to src/components/views/elements/TooltipButton.tsx index c5ebb3b1aa..1232f48695 100644 --- a/src/components/views/elements/TooltipButton.js +++ b/src/components/views/elements/TooltipButton.tsx @@ -19,8 +19,16 @@ import React from 'react'; import * as sdk from '../../../index'; import {replaceableComponent} from "../../../utils/replaceableComponent"; +interface IProps { + helpText: string; +} + +interface IState { + hover: boolean; +} + @replaceableComponent("views.elements.TooltipButton") -export default class TooltipButton extends React.Component { +export default class TooltipButton extends React.Component { state = { hover: false, }; From 36d95ff73799a7dcfc1135a626d4e7b4030c13a5 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 24 May 2021 15:02:26 +0100 Subject: [PATCH 060/159] Display spinner in user menu when joining a room --- src/components/structures/UserMenu.tsx | 58 ++++++++++++++++++++++---- src/i18n/strings/en_EN.json | 2 + 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 65861624e6..c05f74a436 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -57,7 +57,8 @@ import { IHostSignupConfig } from "../views/dialogs/HostSignupDialogTypes"; import SpaceStore, { UPDATE_SELECTED_SPACE } from "../../stores/SpaceStore"; import RoomName from "../views/elements/RoomName"; import {replaceableComponent} from "../../utils/replaceableComponent"; - +import InlineSpinner from "../views/elements/InlineSpinner"; +import TooltipButton from "../views/elements/TooltipButton"; interface IProps { isMinimized: boolean; } @@ -68,6 +69,7 @@ interface IState { contextMenuPosition: PartialDOMRect; isDarkTheme: boolean; selectedSpace?: Room; + pendingRoomJoin: string[] } @replaceableComponent("structures.UserMenu") @@ -84,6 +86,7 @@ export default class UserMenu extends React.Component { this.state = { contextMenuPosition: null, isDarkTheme: this.isUserOnDarkTheme(), + pendingRoomJoin: [], }; OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); @@ -147,15 +150,48 @@ export default class UserMenu extends React.Component { }; private onAction = (ev: ActionPayload) => { - if (ev.action !== Action.ToggleUserMenu) return; // not interested - - if (this.state.contextMenuPosition) { - this.setState({contextMenuPosition: null}); - } else { - if (this.buttonRef.current) this.buttonRef.current.click(); + switch (ev.action) { + case Action.ToggleUserMenu: + if (this.state.contextMenuPosition) { + this.setState({contextMenuPosition: null}); + } else { + if (this.buttonRef.current) this.buttonRef.current.click(); + } + break; + case Action.JoinRoom: + this.addPendingJoinRoom(ev.roomId); + break; + case Action.JoinRoomReady: + case Action.JoinRoomError: + this.removePendingJoinRoom(ev.roomId); + break; } }; + private addPendingJoinRoom(roomId) { + this.setState({ + pendingRoomJoin: [ + ...this.state.pendingRoomJoin, + roomId, + ], + }); + } + + private removePendingJoinRoom(roomId) { + const newPendingRoomJoin = this.state.pendingRoomJoin.filter(pendingJoinRoomId => { + return pendingJoinRoomId !== roomId; + }); + if (newPendingRoomJoin.length !== this.state.pendingRoomJoin.length) { + this.setState({ + pendingRoomJoin: newPendingRoomJoin, + }) + } + } + + get hasPendingActions(): boolean { + return this.state.pendingRoomJoin.length > 0; + } + private onOpenMenuClick = (ev: React.MouseEvent) => { ev.preventDefault(); ev.stopPropagation(); @@ -617,6 +653,14 @@ export default class UserMenu extends React.Component { /> {name} + {this.hasPendingActions && ( + + + + )} {dnd} {buttons}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 7ceb039822..5aba8d998d 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2753,6 +2753,8 @@ "Switch theme": "Switch theme", "User menu": "User menu", "Community and user menu": "Community and user menu", + "Currently joining %(count)s rooms|one": "Currently joining %(count)s room", + "Currently joining %(count)s rooms|other": "Currently joining %(count)s rooms", "Could not load user profile": "Could not load user profile", "Decrypted event source": "Decrypted event source", "Original event source": "Original event source", From f478cd98f7c0faf94e31ef277a09ec0068d7e34c Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 24 May 2021 15:12:17 +0100 Subject: [PATCH 061/159] fix i18n for UserMenu spinner --- src/i18n/strings/en_EN.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 5aba8d998d..1b04ae3b89 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2753,8 +2753,8 @@ "Switch theme": "Switch theme", "User menu": "User menu", "Community and user menu": "Community and user menu", - "Currently joining %(count)s rooms|one": "Currently joining %(count)s room", "Currently joining %(count)s rooms|other": "Currently joining %(count)s rooms", + "Currently joining %(count)s rooms|one": "Currently joining %(count)s room", "Could not load user profile": "Could not load user profile", "Decrypted event source": "Decrypted event source", "Original event source": "Original event source", From 671f1694579253afcc4a6ea8e53bb11fb11f768c Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 24 May 2021 16:08:48 +0100 Subject: [PATCH 062/159] Remove unused middlePanelResized event listener --- src/components/views/rooms/RoomTile.tsx | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index 60368ce250..3ed040c173 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -54,6 +54,7 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; import { getUnsentMessages } from "../../structures/RoomStatusBar"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; import { ResizeNotifier } from "../../../utils/ResizeNotifier"; +import { checkObjectHasNoAdditionalKeys } from "matrix-js-sdk/src/utils"; interface IProps { room: Room; @@ -106,9 +107,6 @@ export default class RoomTile extends React.PureComponent { this.notificationState = RoomNotificationStateStore.instance.getRoomState(this.props.room); this.roomProps = EchoChamber.forRoom(this.props.room); - if (this.props.resizeNotifier) { - this.props.resizeNotifier.on("middlePanelResized", this.onResize); - } } private countUnsentEvents(): number { @@ -123,12 +121,6 @@ export default class RoomTile extends React.PureComponent { this.forceUpdate(); // notification state changed - update }; - private onResize = () => { - if (this.showMessagePreview && !this.state.messagePreview) { - this.generatePreview(); - } - }; - private onLocalEchoUpdated = (ev: MatrixEvent, room: Room) => { if (!room?.roomId === this.props.room.roomId) return; this.setState({hasUnsentEvents: this.countUnsentEvents() > 0}); @@ -148,7 +140,9 @@ export default class RoomTile extends React.PureComponent { } public componentDidUpdate(prevProps: Readonly, prevState: Readonly) { - if (prevProps.showMessagePreview !== this.props.showMessagePreview && this.showMessagePreview) { + const showMessageChanged = prevProps.showMessagePreview !== this.props.showMessagePreview; + const minimizedChanged = prevProps.isMinimized !== this.props.isMinimized; + if (showMessageChanged || minimizedChanged) { this.generatePreview(); } if (prevProps.room?.roomId !== this.props.room?.roomId) { @@ -208,9 +202,6 @@ export default class RoomTile extends React.PureComponent { ); this.props.room.off("Room.name", this.onRoomNameUpdate); } - if (this.props.resizeNotifier) { - this.props.resizeNotifier.off("middlePanelResized", this.onResize); - } ActiveRoomObserver.removeListener(this.props.room.roomId, this.onActiveRoomUpdate); defaultDispatcher.unregister(this.dispatcherRef); this.notificationState.off(NOTIFICATION_STATE_UPDATE, this.onNotificationUpdate); From a0b052d3dea81143a6d766583d7fc28b8a31721b Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Mon, 24 May 2021 11:11:45 -0400 Subject: [PATCH 063/159] bump to olm 3.2.3 --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index aaad80d068..21c1ffb6ea 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,7 @@ "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.2", "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", - "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz", + "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "react-test-renderer": "^16.14.0", "rimraf": "^3.0.2", "stylelint": "^13.9.0", diff --git a/yarn.lock b/yarn.lock index 041bb8b4b9..5b42436039 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1325,9 +1325,9 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz": - version "3.2.2" - resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz#5e3d784461ca3bbeb791ac8f3c175375aeb81318" +"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz": + version "3.2.3" + resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz#cc332fdd25c08ef0e40f4d33fc3f822a0f98b6f4" "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents": version "2.1.8-no-fsevents" From 0bbfb1a6d953344083fb7554a303ee38039adc7d Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 24 May 2021 16:18:55 +0100 Subject: [PATCH 064/159] remove unused variable checkObjectHasNoAdditionalKeys --- src/components/views/rooms/RoomTile.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index 3ed040c173..579a275155 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -54,7 +54,6 @@ import { replaceableComponent } from "../../../utils/replaceableComponent"; import { getUnsentMessages } from "../../structures/RoomStatusBar"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; import { ResizeNotifier } from "../../../utils/ResizeNotifier"; -import { checkObjectHasNoAdditionalKeys } from "matrix-js-sdk/src/utils"; interface IProps { room: Room; From 73221e4d08cb5e7fe71b7df59223d232d403bae3 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Fri, 21 May 2021 16:09:28 -0400 Subject: [PATCH 065/159] Bump libolm version. --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8ecebc4eb3..ba4c8bc98c 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,7 @@ "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.2", "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", - "olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz", + "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz", "react-test-renderer": "^16.14.0", "rimraf": "^3.0.2", "stylelint": "^13.9.0", diff --git a/yarn.lock b/yarn.lock index 0472555dd9..a32b81af4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1325,6 +1325,10 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz": + version "3.2.2" + resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz#5e3d784461ca3bbeb791ac8f3c175375aeb81318" + "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents": version "2.1.8-no-fsevents" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.tgz#da7c3996b8e6e19ebd14d82eaced2313e7769f9b" @@ -6145,10 +6149,6 @@ object.values@^1.1.1, object.values@^1.1.2: es-abstract "^1.18.0-next.1" has "^1.0.3" -"olm@https://packages.matrix.org/npm/olm/olm-3.2.1.tgz": - version "3.2.1" - resolved "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz#d623d76f99c3518dde68be8c86618d68bc7b004a" - once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" From 140258d3d6f53117957ce903fbcd078729a4d2f7 Mon Sep 17 00:00:00 2001 From: Hubert Chathi Date: Mon, 24 May 2021 11:11:45 -0400 Subject: [PATCH 066/159] bump to olm 3.2.3 --- package.json | 2 +- yarn.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index ba4c8bc98c..a8d29eca48 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,7 @@ "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.2", "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", - "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz", + "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "react-test-renderer": "^16.14.0", "rimraf": "^3.0.2", "stylelint": "^13.9.0", diff --git a/yarn.lock b/yarn.lock index a32b81af4b..fe106d7e7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1325,9 +1325,9 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" -"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz": - version "3.2.2" - resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.2.tgz#5e3d784461ca3bbeb791ac8f3c175375aeb81318" +"@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz": + version "3.2.3" + resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz#cc332fdd25c08ef0e40f4d33fc3f822a0f98b6f4" "@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents": version "2.1.8-no-fsevents" From fdc22bfdf747e618dd510c8a257f74ffd8dcca6d Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 24 May 2021 16:40:55 +0100 Subject: [PATCH 067/159] Adhere to TypeScript codestyle better --- src/components/views/elements/TooltipButton.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/views/elements/TooltipButton.tsx b/src/components/views/elements/TooltipButton.tsx index 1232f48695..191018cc19 100644 --- a/src/components/views/elements/TooltipButton.tsx +++ b/src/components/views/elements/TooltipButton.tsx @@ -29,17 +29,20 @@ interface IState { @replaceableComponent("views.elements.TooltipButton") export default class TooltipButton extends React.Component { - state = { - hover: false, - }; + constructor(props) { + super(props); + this.state = { + hover: false, + }; + } - onMouseOver = () => { + private onMouseOver = () => { this.setState({ hover: true, }); }; - onMouseLeave = () => { + private onMouseLeave = () => { this.setState({ hover: false, }); From 3b69c0203c1fba42249b16110db3c7fd976465d2 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 24 May 2021 17:05:59 +0100 Subject: [PATCH 068/159] Remove resize notifier prop from RoomTile --- src/components/views/rooms/RoomTile.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/views/rooms/RoomTile.tsx b/src/components/views/rooms/RoomTile.tsx index 579a275155..aae182eca4 100644 --- a/src/components/views/rooms/RoomTile.tsx +++ b/src/components/views/rooms/RoomTile.tsx @@ -53,14 +53,12 @@ import { CommunityPrototypeStore, IRoomProfile } from "../../../stores/Community import { replaceableComponent } from "../../../utils/replaceableComponent"; import { getUnsentMessages } from "../../structures/RoomStatusBar"; import { StaticNotificationState } from "../../../stores/notifications/StaticNotificationState"; -import { ResizeNotifier } from "../../../utils/ResizeNotifier"; interface IProps { room: Room; showMessagePreview: boolean; isMinimized: boolean; tag: TagID; - resizeNotifier: ResizeNotifier; } type PartialDOMRect = Pick; From ad30e89967e4abc4a3fbab53b17c589f8ad97d0b Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 24 May 2021 17:15:16 +0100 Subject: [PATCH 069/159] Upgrade matrix-js-sdk to 11.1.0 --- package.json | 4 ++-- yarn.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index a8d29eca48..dff6afa609 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "11.1.0-rc.1", + "matrix-js-sdk": "11.1.0", "matrix-widget-api": "^0.1.0-beta.14", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", @@ -121,6 +121,7 @@ "@babel/preset-typescript": "^7.12.7", "@babel/register": "^7.12.10", "@babel/traverse": "^7.12.12", + "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "@peculiar/webcrypto": "^1.1.4", "@sinonjs/fake-timers": "^7.0.2", "@types/classnames": "^2.2.11", @@ -161,7 +162,6 @@ "matrix-mock-request": "^1.2.3", "matrix-react-test-utils": "^0.2.2", "matrix-web-i18n": "github:matrix-org/matrix-web-i18n", - "@matrix-org/olm": "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz", "react-test-renderer": "^16.14.0", "rimraf": "^3.0.2", "stylelint": "^13.9.0", diff --git a/yarn.lock b/yarn.lock index fe106d7e7a..ff7611da84 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5673,10 +5673,10 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@11.1.0-rc.1: - version "11.1.0-rc.1" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-11.1.0-rc.1.tgz#98da580fa51bc8c70c57984e79dc63c617a671d1" - integrity sha512-yyZeL1mHttw+EnZcGfMH+wd33s0+ZKB+KyXHA3QiqiVRWLgANjIY9QCNjbJYa9FK8zZRqO2yHiFLtTAAU379Ag== +matrix-js-sdk@11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-11.1.0.tgz#59119f9fe76adbc38b309947c5532baea8499bf1" + integrity sha512-yBvSGb33MDz9mfbjtVGO7557kgtY/kJcrFyhtN7LwSyi/TDhhYleq5xAqsi7MJrmIb/E0JIF10JIwlF9dAW64Q== dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From d0e41c6461738773c261574dd1da2b1b98d0ff3a Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 24 May 2021 17:20:30 +0100 Subject: [PATCH 070/159] Prepare changelog for v3.22.0 --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b4ec8b457..f3d9afd51d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +Changes in [3.22.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.22.0) (2021-05-24) +===================================================================================================== +[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.22.0-rc.1...v3.22.0) + + * Upgrade to JS SDK 11.1.0 + * [Release] Bump libolm version + [\#6087](https://github.com/matrix-org/matrix-react-sdk/pull/6087) + Changes in [3.22.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.22.0-rc.1) (2021-05-19) =============================================================================================================== [Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.21.0...v3.22.0-rc.1) From fbc01d4930dfd4c9376041ac6b5ef40a038aeb4d Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 24 May 2021 17:20:31 +0100 Subject: [PATCH 071/159] v3.22.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index dff6afa609..5f07c0c0a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "matrix-react-sdk", - "version": "3.22.0-rc.1", + "version": "3.22.0", "description": "SDK for matrix.org using React", "author": "matrix.org", "repository": { From f8ef2d42f5966c06a0ac3a392023bec17bb6fb1c Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 24 May 2021 17:25:58 +0100 Subject: [PATCH 072/159] Resetting package fields for development --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 5f07c0c0a4..e26ce17f42 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "bin": { "reskindex": "scripts/reskindex.js" }, - "main": "./lib/index.js", + "main": "./src/index.js", "matrix_src_main": "./src/index.js", "matrix_lib_main": "./lib/index.js", "matrix_lib_typings": "./lib/index.d.ts", @@ -200,6 +200,5 @@ "coverageReporters": [ "text" ] - }, - "typings": "./lib/index.d.ts" + } } From 7123abc122aa08b51c7847b06d6e3a623b0c5d1e Mon Sep 17 00:00:00 2001 From: RiotRobot Date: Mon, 24 May 2021 17:26:24 +0100 Subject: [PATCH 073/159] Reset matrix-js-sdk back to develop branch --- package.json | 2 +- yarn.lock | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index e26ce17f42..13047b69cf 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "katex": "^0.12.0", "linkifyjs": "^2.1.9", "lodash": "^4.17.20", - "matrix-js-sdk": "11.1.0", + "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop", "matrix-widget-api": "^0.1.0-beta.14", "minimist": "^1.2.5", "opus-recorder": "^8.0.3", diff --git a/yarn.lock b/yarn.lock index ff7611da84..35aac66e26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5673,10 +5673,9 @@ mathml-tag-names@^2.1.3: resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3" integrity sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg== -matrix-js-sdk@11.1.0: +"matrix-js-sdk@github:matrix-org/matrix-js-sdk#develop": version "11.1.0" - resolved "https://registry.yarnpkg.com/matrix-js-sdk/-/matrix-js-sdk-11.1.0.tgz#59119f9fe76adbc38b309947c5532baea8499bf1" - integrity sha512-yBvSGb33MDz9mfbjtVGO7557kgtY/kJcrFyhtN7LwSyi/TDhhYleq5xAqsi7MJrmIb/E0JIF10JIwlF9dAW64Q== + resolved "https://codeload.github.com/matrix-org/matrix-js-sdk/tar.gz/acb9bc8cc5234326a7583514a8e120a4ac42eedc" dependencies: "@babel/runtime" "^7.12.5" another-json "^0.2.0" From cdecc156df1800179a619e2a8063295ece3d63b0 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Mon, 24 May 2021 17:30:37 +0100 Subject: [PATCH 074/159] Remove unused prop --- src/components/views/rooms/RoomSublist.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx index f9881d33ae..8a2059a247 100644 --- a/src/components/views/rooms/RoomSublist.tsx +++ b/src/components/views/rooms/RoomSublist.tsx @@ -530,7 +530,6 @@ export default class RoomSublist extends React.Component { tiles.push( Date: Mon, 24 May 2021 18:57:24 +0100 Subject: [PATCH 075/159] Use local room state to render space hierarchy if the room is known --- .../structures/SpaceRoomDirectory.tsx | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index dde8dd8331..3f1679c97e 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -101,15 +101,13 @@ const Tile: React.FC = ({ numChildRooms, children, }) => { - const name = room.name || room.canonical_alias || room.aliases?.[0] + const cli = MatrixClientPeg.get(); + const joinedRoom = cli.getRoom(room.room_id)?.getMyMembership() === "join" && cli.getRoom(room.room_id); + const name = joinedRoom?.name || room.name || room.canonical_alias || room.aliases?.[0] || (room.room_type === RoomType.Space ? _t("Unnamed Space") : _t("Unnamed Room")); const [showChildren, toggleShowChildren] = useStateToggle(true); - const cli = MatrixClientPeg.get(); - const cliRoom = cli.getRoom(room.room_id); - const myMembership = cliRoom?.getMyMembership(); - const onPreviewClick = (ev: ButtonEvent) => { ev.preventDefault(); ev.stopPropagation(); @@ -122,7 +120,7 @@ const Tile: React.FC = ({ } let button; - if (myMembership === "join") { + if (joinedRoom) { button = { _t("View") } ; @@ -146,17 +144,27 @@ const Tile: React.FC = ({ } } - let url: string; - if (room.avatar_url) { - url = mediaFromMxc(room.avatar_url).getSquareThumbnailHttp(20); + let avatar; + if (joinedRoom) { + avatar = ; + } else { + avatar = ; } let description = _t("%(count)s members", { count: room.num_joined_members }); if (numChildRooms !== undefined) { description += " · " + _t("%(count)s rooms", { count: numChildRooms }); } - if (room.topic) { - description += " · " + room.topic; + + const topic = joinedRoom?.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || room.topic; + if (topic) { + description += " · " + topic; } let suggestedSection; @@ -167,7 +175,7 @@ const Tile: React.FC = ({ } const content = - + { avatar }
{ name } { suggestedSection } From 4be8bbeef9360351873f6a23239c3ab6413fa408 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 24 May 2021 21:17:30 +0100 Subject: [PATCH 076/159] Close creation menu when expanding space panel via expand hierarchy --- src/components/views/spaces/SpacePanel.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index 411b0f9b5e..163a5cfb0b 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -221,14 +221,20 @@ const SpacePanel = () => { space={s} activeSpaces={activeSpaces} isPanelCollapsed={isPanelCollapsed} - onExpand={() => setPanelCollapsed(false)} + onExpand={() => { + closeMenu(); + setPanelCollapsed(false); + }} />) } { spaces.map(s => setPanelCollapsed(false)} + onExpand={() => { + closeMenu(); + setPanelCollapsed(false); + }} />) }
Date: Mon, 22 Feb 2021 17:43:15 +0100 Subject: [PATCH 077/159] Add url param `defaultUsername` to prefill the login username field Signed-off-by: David Schilling --- src/components/structures/MatrixChat.tsx | 1 + src/components/structures/auth/Login.tsx | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 49386c5f65..365ac10d3d 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -2090,6 +2090,7 @@ export default class MatrixChat extends React.PureComponent { onForgotPasswordClick={showPasswordReset ? this.onForgotPasswordClick : undefined} onServerConfigChange={this.onServerConfigChange} fragmentAfterLogin={fragmentAfterLogin} + defaultUsername={this.props.startingFragmentQueryParams.defaultUsername} {...this.getServerProperties()} /> ); diff --git a/src/components/structures/auth/Login.tsx b/src/components/structures/auth/Login.tsx index 34a5410928..d34582b0c3 100644 --- a/src/components/structures/auth/Login.tsx +++ b/src/components/structures/auth/Login.tsx @@ -59,6 +59,7 @@ interface IProps { fallbackHsUrl?: string; defaultDeviceDisplayName?: string; fragmentAfterLogin?: string; + defaultUsername?: string; // Called when the user has logged in. Params: // - The object returned by the login API @@ -119,7 +120,7 @@ export default class LoginComponent extends React.PureComponent flows: null, - username: "", + username: props.defaultUsername? props.defaultUsername: '', phoneCountry: null, phoneNumber: "", From 525e3eaf432db2460d915d7ead21f43be614ef11 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 09:46:49 +0100 Subject: [PATCH 078/159] Prevent reflow when getting screen orientation It is better to access the device orientation using media queries as it will not force a reflow compared to accessing innerWidth/innerHeight --- src/CountlyAnalytics.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/CountlyAnalytics.ts b/src/CountlyAnalytics.ts index 974c08df18..61c471e4d4 100644 --- a/src/CountlyAnalytics.ts +++ b/src/CountlyAnalytics.ts @@ -684,7 +684,9 @@ export default class CountlyAnalytics { } private getOrientation = (): Orientation => { - return window.innerWidth > window.innerHeight ? Orientation.Landscape : Orientation.Portrait; + return window.matchMedia("(orientation: landscape)").matches + ? Orientation.Landscape + : Orientation.Portrait }; private reportOrientation = () => { From 73d51a91d6dbd91ac65cd25df050f071f4a01995 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 09:47:45 +0100 Subject: [PATCH 079/159] Prevent unneeded state updates to hide StickerPicker --- src/components/views/rooms/Stickerpicker.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/Stickerpicker.js b/src/components/views/rooms/Stickerpicker.js index 82e8cf640c..3d2300b83c 100644 --- a/src/components/views/rooms/Stickerpicker.js +++ b/src/components/views/rooms/Stickerpicker.js @@ -40,7 +40,7 @@ const STICKERPICKER_Z_INDEX = 3500; const PERSISTED_ELEMENT_KEY = "stickerPicker"; @replaceableComponent("views.rooms.Stickerpicker") -export default class Stickerpicker extends React.Component { +export default class Stickerpicker extends React.PureComponent { static currentWidget; constructor(props) { @@ -341,21 +341,27 @@ export default class Stickerpicker extends React.Component { * @param {Event} ev Event that triggered the function call */ _onHideStickersClick(ev) { - this.setState({showStickers: false}); + if (this.state.showStickers) { + this.setState({showStickers: false}); + } } /** * Called when the window is resized */ _onResize() { - this.setState({showStickers: false}); + if (this.state.showStickers) { + this.setState({showStickers: false}); + } } /** * The stickers picker was hidden */ _onFinished() { - this.setState({showStickers: false}); + if (this.state.showStickers) { + this.setState({showStickers: false}); + } } /** From 2710062df70892f422da904ee7a1edc226e7b168 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 09:49:15 +0100 Subject: [PATCH 080/159] Create a UIStore to track important data This helper should hold data related to the UI and access save in a smart to avoid performance pitfalls in other parts of the application --- src/stores/UIStore.ts | 69 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 src/stores/UIStore.ts diff --git a/src/stores/UIStore.ts b/src/stores/UIStore.ts new file mode 100644 index 0000000000..62b73a14f6 --- /dev/null +++ b/src/stores/UIStore.ts @@ -0,0 +1,69 @@ +/* +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. +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 EventEmitter from "events"; + +export enum UI_EVENTS { + Resize = "resize" +} + +export type ResizeObserverCallbackFunction = (entries: ResizeObserverEntry[]) => void; + + +export default class UIStore extends EventEmitter { + private static _instance: UIStore = null; + + private resizeObserver: ResizeObserver; + + public windowWith: number; + public windowHeight: number; + + constructor() { + super(); + + this.windowWith = window.innerWidth; + this.windowHeight = window.innerHeight; + + this.resizeObserver = new ResizeObserver(this.resizeObserverCallback); + this.resizeObserver.observe(document.body); + } + + public static get instance(): UIStore { + if (!UIStore._instance) { + UIStore._instance = new UIStore(); + } + return UIStore._instance; + } + + public static destroy(): void { + if (UIStore._instance) { + UIStore._instance.resizeObserver.disconnect(); + UIStore._instance.removeAllListeners(); + UIStore._instance = null; + } + } + + private resizeObserverCallback = (entries: ResizeObserverEntry[]) => { + const { width, height } = entries + .find(entry => entry.target === document.body) + .contentRect; + + this.windowWith = width; + this.windowHeight = height; + + this.emit(UI_EVENTS.Resize, entries); + } +} From ac93cc514f0e0a2be46c300d142496a6325a00f7 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 09:50:09 +0100 Subject: [PATCH 081/159] Prevent layout trashing when resizing the window --- src/components/structures/LeftPanel.tsx | 13 +------------ src/components/structures/LeftPanelWidget.tsx | 10 ++-------- src/components/structures/MatrixChat.tsx | 17 +++++++---------- src/components/views/rooms/RoomList.tsx | 6 +----- src/components/views/rooms/RoomSublist.tsx | 2 -- src/utils/ResizeNotifier.js | 6 ------ 6 files changed, 11 insertions(+), 43 deletions(-) diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 7f9ef7516e..465d4cac49 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -90,10 +90,6 @@ export default class LeftPanel extends React.Component { this.groupFilterPanelWatcherRef = SettingsStore.watchSetting("TagPanel.enableTagPanel", null, () => { this.setState({showGroupFilterPanel: SettingsStore.getValue("TagPanel.enableTagPanel")}); }); - - // We watch the middle panel because we don't actually get resized, the middle panel does. - // We listen to the noisy channel to avoid choppy reaction times. - this.props.resizeNotifier.on("middlePanelResizedNoisy", this.onResize); } public componentWillUnmount() { @@ -103,7 +99,6 @@ export default class LeftPanel extends React.Component { RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); OwnProfileStore.instance.off(UPDATE_EVENT, this.onBackgroundImageUpdate); SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace); - this.props.resizeNotifier.off("middlePanelResizedNoisy", this.onResize); } private updateActiveSpace = (activeSpace: Room) => { @@ -281,11 +276,6 @@ export default class LeftPanel extends React.Component { this.handleStickyHeaders(list); }; - private onResize = () => { - if (!this.listContainerRef.current) return; // ignore: no headers to sticky - this.handleStickyHeaders(this.listContainerRef.current); - }; - private onFocus = (ev: React.FocusEvent) => { this.focusedElement = ev.target; }; @@ -420,7 +410,6 @@ export default class LeftPanel extends React.Component { onFocus={this.onFocus} onBlur={this.onBlur} isMinimized={this.props.isMinimized} - onResize={this.onResize} activeSpace={this.state.activeSpace} />; @@ -454,7 +443,7 @@ export default class LeftPanel extends React.Component { {roomList}
- { !this.props.isMinimized && } + { !this.props.isMinimized && }
); diff --git a/src/components/structures/LeftPanelWidget.tsx b/src/components/structures/LeftPanelWidget.tsx index e88af282ba..89c0744cf8 100644 --- a/src/components/structures/LeftPanelWidget.tsx +++ b/src/components/structures/LeftPanelWidget.tsx @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, {useContext, useEffect, useMemo} from "react"; +import React, {useContext, useMemo} from "react"; import {Resizable} from "re-resizable"; import classNames from "classnames"; @@ -28,15 +28,11 @@ import {useAccountData} from "../../hooks/useAccountData"; import AppTile from "../views/elements/AppTile"; import {useSettingValue} from "../../hooks/useSettings"; -interface IProps { - onResize(): void; -} - const MIN_HEIGHT = 100; const MAX_HEIGHT = 500; // or 50% of the window height const INITIAL_HEIGHT = 280; -const LeftPanelWidget: React.FC = ({ onResize }) => { +const LeftPanelWidget: React.FC = () => { const cli = useContext(MatrixClientContext); const mWidgetsEvent = useAccountData>(cli, "m.widgets"); @@ -56,7 +52,6 @@ const LeftPanelWidget: React.FC = ({ onResize }) => { const [height, setHeight] = useLocalStorageState("left-panel-widget-height", INITIAL_HEIGHT); const [expanded, setExpanded] = useLocalStorageState("left-panel-widget-expanded", true); - useEffect(onResize, [expanded, onResize]); const [onFocus, isActive, ref] = useRovingTabIndex(); const tabIndex = isActive ? 0 : -1; @@ -69,7 +64,6 @@ const LeftPanelWidget: React.FC = ({ onResize }) => { size={{height} as any} minHeight={MIN_HEIGHT} maxHeight={Math.min(window.innerHeight / 2, MAX_HEIGHT)} - onResize={onResize} onResizeStop={(e, dir, ref, d) => { setHeight(height + d.height); }} diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 49386c5f65..1d794b05c4 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -87,6 +87,7 @@ import defaultDispatcher from "../../dispatcher/dispatcher"; import SecurityCustomisations from "../../customisations/Security"; import PerformanceMonitor, { PerformanceEntryNames } from "../../performance"; +import UIStore, { UI_EVENTS } from "../../stores/UIStore"; /** constants for MatrixChat.state.view */ export enum Views { @@ -225,7 +226,6 @@ export default class MatrixChat extends React.PureComponent { firstSyncPromise: IDeferred; private screenAfterLogin?: IScreen; - private windowWidth: number; private pageChanging: boolean; private tokenLogin?: boolean; private accountPassword?: string; @@ -277,9 +277,7 @@ export default class MatrixChat extends React.PureComponent { } } - this.windowWidth = 10000; - this.handleResize(); - window.addEventListener('resize', this.handleResize); + UIStore.instance.on(UI_EVENTS.Resize, this.handleResize); this.pageChanging = false; @@ -436,7 +434,7 @@ export default class MatrixChat extends React.PureComponent { dis.unregister(this.dispatcherRef); this.themeWatcher.stop(); this.fontWatcher.stop(); - window.removeEventListener('resize', this.handleResize); + UIStore.destroy(); this.state.resizeNotifier.removeListener("middlePanelResized", this.dispatchTimelineResize); if (this.accountPasswordTimer !== null) clearTimeout(this.accountPasswordTimer); @@ -1820,18 +1818,17 @@ export default class MatrixChat extends React.PureComponent { } handleResize = () => { - const hideLhsThreshold = 1000; - const showLhsThreshold = 1000; + const LHS_THRESHOLD = 1000; + const width = UIStore.instance.windowWith; - if (this.windowWidth > hideLhsThreshold && window.innerWidth <= hideLhsThreshold) { + if (width <= LHS_THRESHOLD && !this.state.collapseLhs) { dis.dispatch({ action: 'hide_left_panel' }); } - if (this.windowWidth <= showLhsThreshold && window.innerWidth > showLhsThreshold) { + if (width > LHS_THRESHOLD && this.state.collapseLhs) { dis.dispatch({ action: 'show_left_panel' }); } this.state.resizeNotifier.notifyWindowResized(); - this.windowWidth = window.innerWidth; }; private dispatchTimelineResize() { diff --git a/src/components/views/rooms/RoomList.tsx b/src/components/views/rooms/RoomList.tsx index 7b0dadeca5..896021f918 100644 --- a/src/components/views/rooms/RoomList.tsx +++ b/src/components/views/rooms/RoomList.tsx @@ -55,7 +55,6 @@ interface IProps { onKeyDown: (ev: React.KeyboardEvent) => void; onFocus: (ev: React.FocusEvent) => void; onBlur: (ev: React.FocusEvent) => void; - onResize: () => void; resizeNotifier: ResizeNotifier; isMinimized: boolean; activeSpace: Room; @@ -404,9 +403,7 @@ export default class RoomList extends React.PureComponent { const newSublists = objectWithOnly(newLists, newListIds); const sublists = objectShallowClone(newSublists, (k, v) => arrayFastClone(v)); - this.setState({sublists, isNameFiltering}, () => { - this.props.onResize(); - }); + this.setState({sublists, isNameFiltering}); } }; @@ -537,7 +534,6 @@ export default class RoomList extends React.PureComponent { addRoomLabel={aesthetics.addRoomLabel ? _t(aesthetics.addRoomLabel) : aesthetics.addRoomLabel} addRoomContextMenu={aesthetics.addRoomContextMenu} isMinimized={this.props.isMinimized} - onResize={this.props.onResize} showSkeleton={showSkeleton} extraTiles={extraTiles} resizeNotifier={this.props.resizeNotifier} diff --git a/src/components/views/rooms/RoomSublist.tsx b/src/components/views/rooms/RoomSublist.tsx index f9881d33ae..74987b066a 100644 --- a/src/components/views/rooms/RoomSublist.tsx +++ b/src/components/views/rooms/RoomSublist.tsx @@ -74,7 +74,6 @@ interface IProps { addRoomLabel: string; isMinimized: boolean; tagId: TagID; - onResize: () => void; showSkeleton?: boolean; alwaysVisible?: boolean; resizeNotifier: ResizeNotifier; @@ -473,7 +472,6 @@ export default class RoomSublist extends React.Component { private toggleCollapsed = () => { this.layout.isCollapsed = this.state.isExpanded; this.setState({isExpanded: !this.layout.isCollapsed}); - setImmediate(() => this.props.onResize()); // needs to happen when the DOM is updated }; private onHeaderKeyDown = (ev: React.KeyboardEvent) => { diff --git a/src/utils/ResizeNotifier.js b/src/utils/ResizeNotifier.js index fd12a454f6..4d46d10f6c 100644 --- a/src/utils/ResizeNotifier.js +++ b/src/utils/ResizeNotifier.js @@ -74,12 +74,6 @@ export default class ResizeNotifier extends EventEmitter { // can be called in quick succession notifyWindowResized() { - // no need to throttle this one, - // also it could make scrollbars appear for - // a split second when the room list manual layout is now - // taller than the available space - this.emit("leftPanelResized"); - this._updateMiddlePanel(); } } From a57887cc61154bc2e3b280b047b7d45a26cad173 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 09:53:22 +0100 Subject: [PATCH 082/159] Prevent layout trashing on EffectsOverlay --- src/components/views/elements/EffectsOverlay.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/components/views/elements/EffectsOverlay.tsx b/src/components/views/elements/EffectsOverlay.tsx index 7bed0222b0..00d9d147f1 100644 --- a/src/components/views/elements/EffectsOverlay.tsx +++ b/src/components/views/elements/EffectsOverlay.tsx @@ -17,7 +17,8 @@ import React, { FunctionComponent, useEffect, useRef } from 'react'; import dis from '../../../dispatcher/dispatcher'; import ICanvasEffect from '../../../effects/ICanvasEffect'; -import {CHAT_EFFECTS} from '../../../effects' +import { CHAT_EFFECTS } from '../../../effects' +import UIStore, { UI_EVENTS } from "../../../stores/UIStore"; interface IProps { roomWidth: number; @@ -45,8 +46,8 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) => { useEffect(() => { const resize = () => { - if (canvasRef.current) { - canvasRef.current.height = window.innerHeight; + if (canvasRef.current && canvasRef.current?.height !== UIStore.instance.windowHeight) { + canvasRef.current.height = UIStore.instance.windowHeight; } }; const onAction = (payload: { action: string }) => { @@ -58,12 +59,12 @@ const EffectsOverlay: FunctionComponent = ({ roomWidth }) => { } const dispatcherRef = dis.register(onAction); const canvas = canvasRef.current; - canvas.height = window.innerHeight; - window.addEventListener('resize', resize, true); + canvas.height = UIStore.instance.windowHeight; + UIStore.instance.on(UI_EVENTS.Resize, resize); return () => { dis.unregister(dispatcherRef); - window.removeEventListener('resize', resize); + UIStore.instance.off(UI_EVENTS.Resize, resize); // eslint-disable-next-line react-hooks/exhaustive-deps const currentEffects = effectsRef.current; // this is not a react node ref, warning can be safely ignored for (const effect in currentEffects) { From f156c2db15e7e1bef5fd5e444be5dec644aac18d Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 10:25:36 +0100 Subject: [PATCH 083/159] prevent reflow in app when accessing window dimensions --- .eslintrc.js | 18 ++++++++++++++++++ src/components/structures/ContextMenu.tsx | 15 ++++++++------- src/components/structures/LeftPanel.tsx | 4 +++- src/components/structures/LeftPanelWidget.tsx | 3 ++- src/components/structures/MatrixChat.tsx | 2 +- src/components/structures/RoomView.tsx | 3 ++- .../views/directory/NetworkDropdown.tsx | 3 ++- src/components/views/elements/Tooltip.tsx | 7 ++++--- src/components/views/messages/TextualBody.js | 3 ++- .../views/right_panel/RoomSummaryCard.tsx | 5 +++-- src/components/views/right_panel/UserInfo.tsx | 5 +++-- .../views/right_panel/WidgetCard.tsx | 3 ++- src/components/views/rooms/AppsDrawer.js | 3 ++- src/stores/UIStore.ts | 10 +++++++--- 14 files changed, 59 insertions(+), 25 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 4959b133a0..9ae51f9bc5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -30,6 +30,24 @@ module.exports = { "quotes": "off", "no-extra-boolean-cast": "off", + "no-restricted-properties": [ + "error", + ...buildRestrictedPropertiesOptions( + ["window.innerHeight", "window.innerWidth", "window.visualViewport"], + "Use UIStore to access window dimensions instead", + ), + ], }, }], }; + +function buildRestrictedPropertiesOptions(properties, message) { + return properties.map(prop => { + const [object, property] = prop.split("."); + return { + object, + property, + message, + }; + }); +} diff --git a/src/components/structures/ContextMenu.tsx b/src/components/structures/ContextMenu.tsx index ad0f75e162..9d8665c176 100644 --- a/src/components/structures/ContextMenu.tsx +++ b/src/components/structures/ContextMenu.tsx @@ -23,6 +23,7 @@ import classNames from "classnames"; import {Key} from "../../Keyboard"; import {Writeable} from "../../@types/common"; import {replaceableComponent} from "../../utils/replaceableComponent"; +import UIStore from "../../stores/UIStore"; // Shamelessly ripped off Modal.js. There's probably a better way // of doing reusable widgets like dialog boxes & menus where we go and @@ -410,12 +411,12 @@ export const aboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFace.None const buttonBottom = elementRect.bottom + window.pageYOffset; const buttonTop = elementRect.top + window.pageYOffset; // Align the right edge of the menu to the right edge of the button - menuOptions.right = window.innerWidth - buttonRight; + menuOptions.right = UIStore.instance.windowWidth - buttonRight; // Align the menu vertically on whichever side of the button has more space available. - if (buttonBottom < window.innerHeight / 2) { + if (buttonBottom < UIStore.instance.windowHeight / 2) { menuOptions.top = buttonBottom + vPadding; } else { - menuOptions.bottom = (window.innerHeight - buttonTop) + vPadding; + menuOptions.bottom = (UIStore.instance.windowHeight - buttonTop) + vPadding; } return menuOptions; @@ -430,12 +431,12 @@ export const alwaysAboveLeftOf = (elementRect: DOMRect, chevronFace = ChevronFac const buttonBottom = elementRect.bottom + window.pageYOffset; const buttonTop = elementRect.top + window.pageYOffset; // Align the right edge of the menu to the right edge of the button - menuOptions.right = window.innerWidth - buttonRight; + menuOptions.right = UIStore.instance.windowWidth - buttonRight; // Align the menu vertically on whichever side of the button has more space available. - if (buttonBottom < window.innerHeight / 2) { + if (buttonBottom < UIStore.instance.windowHeight / 2) { menuOptions.top = buttonBottom + vPadding; } else { - menuOptions.bottom = (window.innerHeight - buttonTop) + vPadding; + menuOptions.bottom = (UIStore.instance.windowHeight - buttonTop) + vPadding; } return menuOptions; @@ -451,7 +452,7 @@ export const alwaysAboveRightOf = (elementRect: DOMRect, chevronFace = ChevronFa // Align the left edge of the menu to the left edge of the button menuOptions.left = buttonLeft; // Align the menu vertically above the menu - menuOptions.bottom = (window.innerHeight - buttonTop) + vPadding; + menuOptions.bottom = (UIStore.instance.windowHeight - buttonTop) + vPadding; return menuOptions; }; diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 465d4cac49..e929306940 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -43,6 +43,7 @@ import {replaceableComponent} from "../../utils/replaceableComponent"; import {mediaFromMxc} from "../../customisations/Media"; import SpaceStore, {UPDATE_SELECTED_SPACE} from "../../stores/SpaceStore"; import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager"; +import UIStore from "../../stores/UIStore"; interface IProps { isMinimized: boolean; @@ -223,7 +224,8 @@ export default class LeftPanel extends React.Component { header.classList.add("mx_RoomSublist_headerContainer_stickyBottom"); } - const offset = window.innerHeight - (list.parentElement.offsetTop + list.parentElement.offsetHeight); + const offset = UIStore.instance.windowHeight - + (list.parentElement.offsetTop + list.parentElement.offsetHeight); const newBottom = `${offset}px`; if (header.style.bottom !== newBottom) { header.style.bottom = newBottom; diff --git a/src/components/structures/LeftPanelWidget.tsx b/src/components/structures/LeftPanelWidget.tsx index 89c0744cf8..16142069c4 100644 --- a/src/components/structures/LeftPanelWidget.tsx +++ b/src/components/structures/LeftPanelWidget.tsx @@ -27,6 +27,7 @@ import WidgetUtils, {IWidgetEvent} from "../../utils/WidgetUtils"; import {useAccountData} from "../../hooks/useAccountData"; import AppTile from "../views/elements/AppTile"; import {useSettingValue} from "../../hooks/useSettings"; +import UIStore from "../../stores/UIStore"; const MIN_HEIGHT = 100; const MAX_HEIGHT = 500; // or 50% of the window height @@ -63,7 +64,7 @@ const LeftPanelWidget: React.FC = () => { content = { setHeight(height + d.height); }} diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 1d794b05c4..c01437b313 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -1819,7 +1819,7 @@ export default class MatrixChat extends React.PureComponent { handleResize = () => { const LHS_THRESHOLD = 1000; - const width = UIStore.instance.windowWith; + const width = UIStore.instance.windowWidth; if (width <= LHS_THRESHOLD && !this.state.collapseLhs) { dis.dispatch({ action: 'hide_left_panel' }); diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index d822b6a839..e3b0c10fb2 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -83,6 +83,7 @@ import { objectHasDiff } from "../../utils/objects"; import SpaceRoomView from "./SpaceRoomView"; import { IOpts } from "../../createRoom"; import {replaceableComponent} from "../../utils/replaceableComponent"; +import UIStore from "../../stores/UIStore"; const DEBUG = false; let debuglog = function(msg: string) {}; @@ -1585,7 +1586,7 @@ export default class RoomView extends React.Component { // a maxHeight on the underlying remote video tag. // header + footer + status + give us at least 120px of scrollback at all times. - let auxPanelMaxHeight = window.innerHeight - + let auxPanelMaxHeight = UIStore.instance.windowHeight - (54 + // height of RoomHeader 36 + // height of the status area 51 + // minimum height of the message compmoser diff --git a/src/components/views/directory/NetworkDropdown.tsx b/src/components/views/directory/NetworkDropdown.tsx index 66b7321ce0..08787812f6 100644 --- a/src/components/views/directory/NetworkDropdown.tsx +++ b/src/components/views/directory/NetworkDropdown.tsx @@ -38,13 +38,14 @@ import withValidation from "../elements/Validation"; import { SettingLevel } from "../../../settings/SettingLevel"; import TextInputDialog from "../dialogs/TextInputDialog"; import QuestionDialog from "../dialogs/QuestionDialog"; +import UIStore from "../../../stores/UIStore"; export const ALL_ROOMS = Symbol("ALL_ROOMS"); const SETTING_NAME = "room_directory_servers"; const inPlaceOf = (elementRect: Pick) => ({ - right: window.innerWidth - elementRect.right, + right: UIStore.instance.windowWidth - elementRect.right, top: elementRect.top, chevronOffset: 0, chevronFace: ChevronFace.None, diff --git a/src/components/views/elements/Tooltip.tsx b/src/components/views/elements/Tooltip.tsx index 062d26c852..7e9ce9745c 100644 --- a/src/components/views/elements/Tooltip.tsx +++ b/src/components/views/elements/Tooltip.tsx @@ -22,6 +22,7 @@ import React, {Component, CSSProperties} from 'react'; import ReactDOM from 'react-dom'; import classNames from 'classnames'; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import UIStore from "../../../stores/UIStore"; const MIN_TOOLTIP_HEIGHT = 25; @@ -97,15 +98,15 @@ export default class Tooltip extends React.Component { // we need so that we're still centered. offset = Math.floor(parentBox.height - MIN_TOOLTIP_HEIGHT); } - + const width = UIStore.instance.windowWidth; const baseTop = (parentBox.top - 2 + this.props.yOffset) + window.pageYOffset; const top = baseTop + offset; - const right = window.innerWidth - parentBox.right - window.pageXOffset - 16; + const right = width - parentBox.right - window.pageXOffset - 16; const left = parentBox.right + window.pageXOffset + 6; const horizontalCenter = parentBox.right - window.pageXOffset - (parentBox.width / 2); switch (this.props.alignment) { case Alignment.Natural: - if (parentBox.right > window.innerWidth / 2) { + if (parentBox.right > width / 2) { style.right = right; style.top = top; break; diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index b963e741a1..dc644f1009 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -36,6 +36,7 @@ import {toRightOf} from "../../structures/ContextMenu"; import {copyPlaintext} from "../../../utils/strings"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import {replaceableComponent} from "../../../utils/replaceableComponent"; +import UIStore from "../../../stores/UIStore"; @replaceableComponent("views.messages.TextualBody") export default class TextualBody extends React.Component { @@ -143,7 +144,7 @@ export default class TextualBody extends React.Component { _addCodeExpansionButton(div, pre) { // Calculate how many percent does the pre element take up. // If it's less than 30% we don't add the expansion button. - const percentageOfViewport = pre.offsetHeight / window.innerHeight * 100; + const percentageOfViewport = pre.offsetHeight / UIStore.instance.windowHeight * 100; if (percentageOfViewport < 30) return; const button = document.createElement("span"); diff --git a/src/components/views/right_panel/RoomSummaryCard.tsx b/src/components/views/right_panel/RoomSummaryCard.tsx index 88928290f4..937037f644 100644 --- a/src/components/views/right_panel/RoomSummaryCard.tsx +++ b/src/components/views/right_panel/RoomSummaryCard.tsx @@ -46,6 +46,7 @@ import WidgetContextMenu from "../context_menus/WidgetContextMenu"; import {useRoomMemberCount} from "../../../hooks/useRoomMembers"; import { Container, MAX_PINNED, WidgetLayoutStore } from "../../../stores/widgets/WidgetLayoutStore"; import RoomName from "../elements/RoomName"; +import UIStore from "../../../stores/UIStore"; interface IProps { room: Room; @@ -116,8 +117,8 @@ const AppRow: React.FC = ({ app, room }) => { const rect = handle.current.getBoundingClientRect(); contextMenu = ; diff --git a/src/components/views/right_panel/UserInfo.tsx b/src/components/views/right_panel/UserInfo.tsx index 9798b282f6..6e56b9259b 100644 --- a/src/components/views/right_panel/UserInfo.tsx +++ b/src/components/views/right_panel/UserInfo.tsx @@ -66,6 +66,7 @@ import { SetRightPanelPhasePayload } from "../../../dispatcher/payloads/SetRight import RoomAvatar from "../avatars/RoomAvatar"; import RoomName from "../elements/RoomName"; import {mediaFromMxc} from "../../../customisations/Media"; +import UIStore from "../../../stores/UIStore"; export interface IDevice { deviceId: string; @@ -1448,8 +1449,8 @@ const UserInfoHeader: React.FC<{ = ({ room, widgetId, onClose }) => { contextMenu = ( entry.target === document.body) .contentRect; - this.windowWith = width; + this.windowWidth = width; this.windowHeight = height; this.emit(UI_EVENTS.Resize, entries); From 45678de9a12cd3ea748a09b2c8b99a9869346262 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 25 May 2021 11:29:54 +0100 Subject: [PATCH 084/159] Stop overscroll in Firefox Nightly for macOS Firefox is working on an overscroll feature for macOS, similar to the one Safari has had for some time now. It doesn't really make sense in an application context, so this disables it. --- res/css/_common.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/res/css/_common.scss b/res/css/_common.scss index d6f85edb86..a05ec7eadd 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -45,6 +45,8 @@ html { N.B. Breaks things when we have legitimate horizontal overscroll */ height: 100%; overflow: hidden; + // Stop similar overscroll bounce in Firefox Nightly for macOS + overscroll-behavior: none; } body { From 85a73f2504408b58405762d1d54d36f3f358be1f Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 11:48:45 +0100 Subject: [PATCH 085/159] Fix copyright header in UIStore file --- src/stores/UIStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/UIStore.ts b/src/stores/UIStore.ts index 8e3a6fb31a..e7f6070627 100644 --- a/src/stores/UIStore.ts +++ b/src/stores/UIStore.ts @@ -1,5 +1,5 @@ /* -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2021 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 cb88f37bbd146c62c4ead8043b10b1c3496be2e7 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Tue, 25 May 2021 12:28:16 +0100 Subject: [PATCH 086/159] Remove outdated diagnostic log The cited issue (https://github.com/vector-im/element-web/issues/11120) has since been fixed, so this "temporary" (2 years ago) logging is no longer needed. --- src/components/views/rooms/EventTile.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/components/views/rooms/EventTile.tsx b/src/components/views/rooms/EventTile.tsx index 19c5a7acaa..67df5a84ba 100644 --- a/src/components/views/rooms/EventTile.tsx +++ b/src/components/views/rooms/EventTile.tsx @@ -790,13 +790,6 @@ export default class EventTile extends React.Component { return null; } const eventId = this.props.mxEvent.getId(); - if (!eventId) { - // XXX: Temporary diagnostic logging for https://github.com/vector-im/element-web/issues/11120 - console.error("EventTile attempted to get relations for an event without an ID"); - // Use event's special `toJSON` method to log key data. - console.log(JSON.stringify(this.props.mxEvent, null, 4)); - console.trace("Stacktrace for https://github.com/vector-im/element-web/issues/11120"); - } return this.props.getRelationsForEvent(eventId, "m.annotation", "m.reaction"); }; From 88af74e4a4b07325246ff6e8f9301f60c7955f97 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 12:45:19 +0100 Subject: [PATCH 087/159] Improve addEventsToTimeline performance scoping WhoIsTypingTile::setState --- src/components/views/rooms/WhoIsTypingTile.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/views/rooms/WhoIsTypingTile.js b/src/components/views/rooms/WhoIsTypingTile.js index a25b43fc3a..e69406505e 100644 --- a/src/components/views/rooms/WhoIsTypingTile.js +++ b/src/components/views/rooms/WhoIsTypingTile.js @@ -87,7 +87,9 @@ export default class WhoIsTypingTile extends React.Component { const userId = event.getSender(); // remove user from usersTyping const usersTyping = this.state.usersTyping.filter((m) => m.userId !== userId); - this.setState({usersTyping}); + if (usersTyping.length !== this.state.usersTyping.length) { + this.setState({usersTyping}); + } // abort timer if any this._abortUserTimer(userId); } From 7303166924b0c6ea492811f768354e27b35cd974 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 13:53:20 +0100 Subject: [PATCH 088/159] fix sticky headers when results num get displayed --- res/css/views/rooms/_RoomSublist.scss | 2 +- src/components/structures/LeftPanel.tsx | 15 ++++++--------- src/components/views/rooms/RoomListNumResults.tsx | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/res/css/views/rooms/_RoomSublist.scss b/res/css/views/rooms/_RoomSublist.scss index 1aafa8da0e..fa94425659 100644 --- a/res/css/views/rooms/_RoomSublist.scss +++ b/res/css/views/rooms/_RoomSublist.scss @@ -62,7 +62,7 @@ limitations under the License. position: fixed; height: 32px; // to match the header container // width set by JS - width: calc(100% - 22px); + width: calc(100% - 15px); } // We don't have a top style because the top is dependent on the room list header's diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index e929306940..4d7b80726f 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -110,6 +110,11 @@ export default class LeftPanel extends React.Component { dis.fire(Action.ViewRoomDirectory); }; + private refreshStickyHeaders = () => { + if (!this.listContainerRef.current) return; // ignore: no headers to sticky + this.handleStickyHeaders(this.listContainerRef.current); + } + private onBreadcrumbsUpdate = () => { const newVal = BreadcrumbsStore.instance.visible; if (newVal !== this.state.showBreadcrumbs) { @@ -243,18 +248,10 @@ export default class LeftPanel extends React.Component { if (!header.classList.contains("mx_RoomSublist_headerContainer_sticky")) { header.classList.add("mx_RoomSublist_headerContainer_sticky"); } - - const newWidth = `${headerStickyWidth}px`; - if (header.style.width !== newWidth) { - header.style.width = newWidth; - } } else if (!style.stickyTop && !style.stickyBottom) { if (header.classList.contains("mx_RoomSublist_headerContainer_sticky")) { header.classList.remove("mx_RoomSublist_headerContainer_sticky"); } - if (header.style.width) { - header.style.removeProperty('width'); - } } } @@ -432,7 +429,7 @@ export default class LeftPanel extends React.Component { {this.renderHeader()} {this.renderSearchExplore()} {this.renderBreadcrumbs()} - +
{ +interface IProps { + onVisibilityChange?: () => void +} + +const RoomListNumResults: React.FC = ({ onVisibilityChange }) => { const [count, setCount] = useState(null); useEventEmitter(RoomListStore.instance, LISTS_UPDATE_EVENT, () => { if (RoomListStore.instance.getFirstNameFilterCondition()) { @@ -32,6 +36,12 @@ const RoomListNumResults: React.FC = () => { } }); + useEffect(() => { + if (onVisibilityChange) { + onVisibilityChange(); + } + }, [count, onVisibilityChange]); + if (typeof count !== "number") return null; return
From a803e33ffe3ae4b17548a69161ae9c0cb2c160f8 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 14:10:16 +0100 Subject: [PATCH 089/159] Convert WhoIsTypingTile to TypeScript --- ...WhoIsTypingTile.js => WhoIsTypingTile.tsx} | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) rename src/components/views/rooms/{WhoIsTypingTile.js => WhoIsTypingTile.tsx} (83%) diff --git a/src/components/views/rooms/WhoIsTypingTile.js b/src/components/views/rooms/WhoIsTypingTile.tsx similarity index 83% rename from src/components/views/rooms/WhoIsTypingTile.js rename to src/components/views/rooms/WhoIsTypingTile.tsx index e69406505e..eaade3016b 100644 --- a/src/components/views/rooms/WhoIsTypingTile.js +++ b/src/components/views/rooms/WhoIsTypingTile.tsx @@ -16,36 +16,44 @@ limitations under the License. */ import React from 'react'; -import PropTypes from 'prop-types'; import * as WhoIsTyping from '../../../WhoIsTyping'; import Timer from '../../../utils/Timer'; -import {MatrixClientPeg} from '../../../MatrixClientPeg'; +import { MatrixClientPeg } from '../../../MatrixClientPeg'; import MemberAvatar from '../avatars/MemberAvatar'; -import {replaceableComponent} from "../../../utils/replaceableComponent"; +import { replaceableComponent } from "../../../utils/replaceableComponent"; + +import Room from "matrix-js-sdk/src/models/room"; +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + +interface IProps { + // the room this statusbar is representing. + room: Room, + onShown?: () => void, + onHidden?: () => void, + // Number of names to display in typing indication. E.g. set to 3, will + // result in "X, Y, Z and 100 others are typing." + whoIsTypingLimit: number, +} + +interface IState { + usersTyping: RoomMember[], + // a map with userid => Timer to delay + // hiding the "x is typing" message for a + // user so hiding it can coincide + // with the sent message by the other side + // resulting in less timeline jumpiness + delayedStopTypingTimers: any +} @replaceableComponent("views.rooms.WhoIsTypingTile") -export default class WhoIsTypingTile extends React.Component { - static propTypes = { - // the room this statusbar is representing. - room: PropTypes.object.isRequired, - onShown: PropTypes.func, - onHidden: PropTypes.func, - // Number of names to display in typing indication. E.g. set to 3, will - // result in "X, Y, Z and 100 others are typing." - whoIsTypingLimit: PropTypes.number, - }; - +export default class WhoIsTypingTile extends React.Component { static defaultProps = { whoIsTypingLimit: 3, }; state = { usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room), - // a map with userid => Timer to delay - // hiding the "x is typing" message for a - // user so hiding it can coincide - // with the sent message by the other side - // resulting in less timeline jumpiness delayedStopTypingTimers: {}, }; @@ -74,15 +82,15 @@ export default class WhoIsTypingTile extends React.Component { Object.values(this.state.delayedStopTypingTimers).forEach((t) => t.abort()); } - _isVisible(state) { + _isVisible(state: IState): boolean { return state.usersTyping.length !== 0 || Object.keys(state.delayedStopTypingTimers).length !== 0; } - isVisible = () => { + isVisible: () => boolean = () => { return this._isVisible(this.state); }; - onRoomTimeline = (event, room) => { + onRoomTimeline = (event: MatrixEvent, room: Room): void => { if (room?.roomId === this.props.room?.roomId) { const userId = event.getSender(); // remove user from usersTyping @@ -95,7 +103,7 @@ export default class WhoIsTypingTile extends React.Component { } }; - onRoomMemberTyping = (ev, member) => { + onRoomMemberTyping = (): void => { const usersTyping = WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room); this.setState({ delayedStopTypingTimers: this._updateDelayedStopTypingTimers(usersTyping), @@ -103,7 +111,7 @@ export default class WhoIsTypingTile extends React.Component { }); }; - _updateDelayedStopTypingTimers(usersTyping) { + _updateDelayedStopTypingTimers(usersTyping: RoomMember[]): void { const usersThatStoppedTyping = this.state.usersTyping.filter((a) => { return !usersTyping.some((b) => a.userId === b.userId); }); @@ -141,7 +149,7 @@ export default class WhoIsTypingTile extends React.Component { return delayedStopTypingTimers; } - _abortUserTimer(userId) { + _abortUserTimer(userId: string): void { const timer = this.state.delayedStopTypingTimers[userId]; if (timer) { timer.abort(); @@ -149,7 +157,7 @@ export default class WhoIsTypingTile extends React.Component { } } - _removeUserTimer(userId) { + _removeUserTimer(userId: string): void { const timer = this.state.delayedStopTypingTimers[userId]; if (timer) { const delayedStopTypingTimers = Object.assign({}, this.state.delayedStopTypingTimers); @@ -158,7 +166,7 @@ export default class WhoIsTypingTile extends React.Component { } } - _renderTypingIndicatorAvatars(users, limit) { + _renderTypingIndicatorAvatars(users: RoomMember[], limit: number): void { let othersCount = 0; if (users.length > limit) { othersCount = users.length - limit + 1; From d6443384213fc13bafcaf0830ed1f832c1fe08ec Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 14:34:19 +0100 Subject: [PATCH 090/159] WhoIsTypingTile TypeScript conversion --- .../views/rooms/WhoIsTypingTile.tsx | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/components/views/rooms/WhoIsTypingTile.tsx b/src/components/views/rooms/WhoIsTypingTile.tsx index eaade3016b..21afbc30f4 100644 --- a/src/components/views/rooms/WhoIsTypingTile.tsx +++ b/src/components/views/rooms/WhoIsTypingTile.tsx @@ -16,34 +16,34 @@ limitations under the License. */ import React from 'react'; +import Room from "matrix-js-sdk/src/models/room"; +import { RoomMember } from "matrix-js-sdk/src/models/room-member"; +import { MatrixEvent } from "matrix-js-sdk/src/models/event"; + import * as WhoIsTyping from '../../../WhoIsTyping'; import Timer from '../../../utils/Timer'; import { MatrixClientPeg } from '../../../MatrixClientPeg'; import MemberAvatar from '../avatars/MemberAvatar'; import { replaceableComponent } from "../../../utils/replaceableComponent"; -import Room from "matrix-js-sdk/src/models/room"; -import { RoomMember } from "matrix-js-sdk/src/models/room-member"; -import { MatrixEvent } from "matrix-js-sdk/src/models/event"; - interface IProps { // the room this statusbar is representing. - room: Room, - onShown?: () => void, - onHidden?: () => void, + room: Room; + onShown?: () => void; + onHidden?: () => void; // Number of names to display in typing indication. E.g. set to 3, will // result in "X, Y, Z and 100 others are typing." - whoIsTypingLimit: number, + whoIsTypingLimit: number; } interface IState { - usersTyping: RoomMember[], + usersTyping: RoomMember[]; // a map with userid => Timer to delay // hiding the "x is typing" message for a // user so hiding it can coincide // with the sent message by the other side // resulting in less timeline jumpiness - delayedStopTypingTimers: any + delayedStopTypingTimers: Record; } @replaceableComponent("views.rooms.WhoIsTypingTile") @@ -79,18 +79,18 @@ export default class WhoIsTypingTile extends React.Component { client.removeListener("RoomMember.typing", this.onRoomMemberTyping); client.removeListener("Room.timeline", this.onRoomTimeline); } - Object.values(this.state.delayedStopTypingTimers).forEach((t) => t.abort()); + Object.values(this.state.delayedStopTypingTimers).forEach((t) => (t as Timer).abort()); } - _isVisible(state: IState): boolean { + private _isVisible(state: IState): boolean { return state.usersTyping.length !== 0 || Object.keys(state.delayedStopTypingTimers).length !== 0; } - isVisible: () => boolean = () => { + public isVisible = (): boolean => { return this._isVisible(this.state); }; - onRoomTimeline = (event: MatrixEvent, room: Room): void => { + private onRoomTimeline = (event: MatrixEvent, room: Room): void => { if (room?.roomId === this.props.room?.roomId) { const userId = event.getSender(); // remove user from usersTyping @@ -99,19 +99,19 @@ export default class WhoIsTypingTile extends React.Component { this.setState({usersTyping}); } // abort timer if any - this._abortUserTimer(userId); + this.abortUserTimer(userId); } }; - onRoomMemberTyping = (): void => { + private onRoomMemberTyping = (): void => { const usersTyping = WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room); this.setState({ - delayedStopTypingTimers: this._updateDelayedStopTypingTimers(usersTyping), + delayedStopTypingTimers: this.updateDelayedStopTypingTimers(usersTyping), usersTyping, }); }; - _updateDelayedStopTypingTimers(usersTyping: RoomMember[]): void { + private updateDelayedStopTypingTimers(usersTyping: RoomMember[]): Record { const usersThatStoppedTyping = this.state.usersTyping.filter((a) => { return !usersTyping.some((b) => a.userId === b.userId); }); @@ -139,7 +139,7 @@ export default class WhoIsTypingTile extends React.Component { delayedStopTypingTimers[m.userId] = timer; timer.start(); timer.finished().then( - () => this._removeUserTimer(m.userId), // on elapsed + () => this.removeUserTimer(m.userId), // on elapsed () => {/* aborted */}, ); } @@ -149,15 +149,15 @@ export default class WhoIsTypingTile extends React.Component { return delayedStopTypingTimers; } - _abortUserTimer(userId: string): void { + private abortUserTimer(userId: string): void { const timer = this.state.delayedStopTypingTimers[userId]; if (timer) { timer.abort(); - this._removeUserTimer(userId); + this.removeUserTimer(userId); } } - _removeUserTimer(userId: string): void { + private removeUserTimer(userId: string): void { const timer = this.state.delayedStopTypingTimers[userId]; if (timer) { const delayedStopTypingTimers = Object.assign({}, this.state.delayedStopTypingTimers); @@ -166,7 +166,7 @@ export default class WhoIsTypingTile extends React.Component { } } - _renderTypingIndicatorAvatars(users: RoomMember[], limit: number): void { + private renderTypingIndicatorAvatars(users: RoomMember[], limit: number): JSX.Element[] { let othersCount = 0; if (users.length > limit) { othersCount = users.length - limit + 1; @@ -220,7 +220,7 @@ export default class WhoIsTypingTile extends React.Component { return (
  • - { this._renderTypingIndicatorAvatars(usersTyping, this.props.whoIsTypingLimit) } + { this.renderTypingIndicatorAvatars(usersTyping, this.props.whoIsTypingLimit) }
    { typingString } From b09dd8f1f89046ac0c94c1eb71e6b4025f557623 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 14:54:27 +0100 Subject: [PATCH 091/159] remove unused values --- src/components/structures/LeftPanel.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 4d7b80726f..22c60bff1e 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -157,9 +157,6 @@ export default class LeftPanel extends React.Component { const bottomEdge = list.offsetHeight + list.scrollTop; const sublists = list.querySelectorAll(".mx_RoomSublist:not(.mx_RoomSublist_hidden)"); - const headerRightMargin = 15; // calculated from margins and widths to align with non-sticky tiles - const headerStickyWidth = list.clientWidth - headerRightMargin; - // We track which styles we want on a target before making the changes to avoid // excessive layout updates. const targetStyles = new Map Date: Tue, 25 May 2021 14:57:07 +0100 Subject: [PATCH 092/159] remove CSS out of sync comment --- res/css/views/rooms/_RoomSublist.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/res/css/views/rooms/_RoomSublist.scss b/res/css/views/rooms/_RoomSublist.scss index fa94425659..b3e907af04 100644 --- a/res/css/views/rooms/_RoomSublist.scss +++ b/res/css/views/rooms/_RoomSublist.scss @@ -61,7 +61,6 @@ limitations under the License. &.mx_RoomSublist_headerContainer_sticky { position: fixed; height: 32px; // to match the header container - // width set by JS width: calc(100% - 15px); } From 17bbbff4797fac7b796f9d1b773b6cf0cefc4783 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Tue, 25 May 2021 16:12:34 +0100 Subject: [PATCH 093/159] Remove Promise allSettled polyfill as its widespread enough now and js-sdk uses it directly --- src/GroupAddressPicker.js | 3 +-- src/components/structures/GroupView.js | 6 +++--- .../views/dialogs/SpaceSettingsDialog.tsx | 3 +-- src/utils/promise.ts | 18 ------------------ 4 files changed, 5 insertions(+), 25 deletions(-) diff --git a/src/GroupAddressPicker.js b/src/GroupAddressPicker.js index d956189f0d..9497d9de4c 100644 --- a/src/GroupAddressPicker.js +++ b/src/GroupAddressPicker.js @@ -21,7 +21,6 @@ import MultiInviter from './utils/MultiInviter'; import { _t } from './languageHandler'; import {MatrixClientPeg} from './MatrixClientPeg'; import GroupStore from './stores/GroupStore'; -import {allSettled} from "./utils/promise"; import StyledCheckbox from './components/views/elements/StyledCheckbox'; export function showGroupInviteDialog(groupId) { @@ -120,7 +119,7 @@ function _onGroupInviteFinished(groupId, addrs) { function _onGroupAddRoomFinished(groupId, addrs, addRoomsPublicly) { const matrixClient = MatrixClientPeg.get(); const errorList = []; - return allSettled(addrs.map((addr) => { + return Promise.allSettled(addrs.map((addr) => { return GroupStore .addRoomToGroup(groupId, addr.address, addRoomsPublicly) .catch(() => { errorList.push(addr.address); }) diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js index 3ab009d7b8..3a2c611cc9 100644 --- a/src/components/structures/GroupView.js +++ b/src/components/structures/GroupView.js @@ -36,7 +36,7 @@ import FlairStore from '../../stores/FlairStore'; import { showGroupAddRoomDialog } from '../../GroupAddressPicker'; import {makeGroupPermalink, makeUserPermalink} from "../../utils/permalinks/Permalinks"; import {Group} from "matrix-js-sdk/src/models/group"; -import {allSettled, sleep} from "../../utils/promise"; +import {sleep} from "../../utils/promise"; import RightPanelStore from "../../stores/RightPanelStore"; import AutoHideScrollbar from "./AutoHideScrollbar"; import {mediaFromMxc} from "../../customisations/Media"; @@ -99,7 +99,7 @@ class CategoryRoomList extends React.Component { onFinished: (success, addrs) => { if (!success) return; const errorList = []; - allSettled(addrs.map((addr) => { + Promise.allSettled(addrs.map((addr) => { return GroupStore .addRoomToGroupSummary(this.props.groupId, addr.address) .catch(() => { errorList.push(addr.address); }); @@ -274,7 +274,7 @@ class RoleUserList extends React.Component { onFinished: (success, addrs) => { if (!success) return; const errorList = []; - allSettled(addrs.map((addr) => { + Promise.allSettled(addrs.map((addr) => { return GroupStore .addUserToGroupSummary(addr.address) .catch(() => { errorList.push(addr.address); }); diff --git a/src/components/views/dialogs/SpaceSettingsDialog.tsx b/src/components/views/dialogs/SpaceSettingsDialog.tsx index dc6052650a..7453ff1d8b 100644 --- a/src/components/views/dialogs/SpaceSettingsDialog.tsx +++ b/src/components/views/dialogs/SpaceSettingsDialog.tsx @@ -30,7 +30,6 @@ import ToggleSwitch from "../elements/ToggleSwitch"; import AccessibleButton from "../elements/AccessibleButton"; import Modal from "../../../Modal"; import defaultDispatcher from "../../../dispatcher/dispatcher"; -import {allSettled} from "../../../utils/promise"; import {useDispatcher} from "../../../hooks/useDispatcher"; import {SpaceFeedbackPrompt} from "../../structures/SpaceRoomView"; @@ -91,7 +90,7 @@ const SpaceSettingsDialog: React.FC = ({ matrixClient: cli, space, onFin promises.push(cli.sendStateEvent(space.roomId, EventType.RoomJoinRules, { join_rule: joinRule }, "")); } - const results = await allSettled(promises); + const results = await Promise.allSettled(promises); setBusy(false); const failures = results.filter(r => r.status === "rejected"); if (failures.length > 0) { diff --git a/src/utils/promise.ts b/src/utils/promise.ts index f828ddfdaf..4ebbb27141 100644 --- a/src/utils/promise.ts +++ b/src/utils/promise.ts @@ -51,24 +51,6 @@ export function defer(): IDeferred { return {resolve, reject, promise}; } -// Promise.allSettled polyfill until browser support is stable in Firefox -export function allSettled(promises: Promise[]): Promise | ISettledRejected>> { - if (Promise.allSettled) { - return Promise.allSettled(promises); - } - - // @ts-ignore - typescript isn't smart enough to see the disjoint here - return Promise.all(promises.map((promise) => { - return promise.then(value => ({ - status: "fulfilled", - value, - })).catch(reason => ({ - status: "rejected", - reason, - })); - })); -} - // Helper method to retry a Promise a given number of times or until a predicate fails export async function retry(fn: () => Promise, num: number, predicate?: (e: E) => boolean) { let lastErr: E; From e934f81521dbe50dbdfca807fce003fb1f816573 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 16:34:52 +0100 Subject: [PATCH 094/159] Skip generatePreview if event is not part of the live timeline --- src/stores/room-list/MessagePreviewStore.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index 10e5cf554e..4de612c7bd 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -176,7 +176,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient { if (payload.action === 'MatrixActions.Room.timeline' || payload.action === 'MatrixActions.Event.decrypted') { const event = payload.event; // TODO: Type out the dispatcher - if (!this.previews.has(event.getRoomId())) return; // not important + if (!this.previews.has(event.getRoomId()) || !event.isLiveEvent) return; // not important await this.generatePreview(this.matrixClient.getRoom(event.getRoomId()), TAG_ANY); } } From 80bd1304213b6b9818a1ad4a5bf6cc855422114b Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Tue, 25 May 2021 16:58:23 +0100 Subject: [PATCH 095/159] Prevent DecoratedRoomAvatar to update its state for the same value --- src/components/views/avatars/DecoratedRoomAvatar.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/views/avatars/DecoratedRoomAvatar.tsx b/src/components/views/avatars/DecoratedRoomAvatar.tsx index f15538eabf..42aef24086 100644 --- a/src/components/views/avatars/DecoratedRoomAvatar.tsx +++ b/src/components/views/avatars/DecoratedRoomAvatar.tsx @@ -119,7 +119,10 @@ export default class DecoratedRoomAvatar extends React.PureComponent Date: Tue, 25 May 2021 17:26:43 +0100 Subject: [PATCH 096/159] Fix accessing currentState on an invalid joinedRoom --- src/components/structures/SpaceRoomDirectory.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index 3f1679c97e..3985553b20 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -102,7 +102,7 @@ const Tile: React.FC = ({ children, }) => { const cli = MatrixClientPeg.get(); - const joinedRoom = cli.getRoom(room.room_id)?.getMyMembership() === "join" && cli.getRoom(room.room_id); + const joinedRoom = cli.getRoom(room.room_id)?.getMyMembership() === "join" ? cli.getRoom(room.room_id) : null; const name = joinedRoom?.name || room.name || room.canonical_alias || room.aliases?.[0] || (room.room_type === RoomType.Space ? _t("Unnamed Space") : _t("Unnamed Room")); @@ -162,7 +162,7 @@ const Tile: React.FC = ({ description += " · " + _t("%(count)s rooms", { count: numChildRooms }); } - const topic = joinedRoom?.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || room.topic; + const topic = joinedRoom?.currentState?.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || room.topic; if (topic) { description += " · " + topic; } From a4907f8061a1d0e2ec84c6edf5f9669fa7bbc821 Mon Sep 17 00:00:00 2001 From: Jaiwanth Date: Wed, 26 May 2021 12:57:39 +0530 Subject: [PATCH 097/159] Destroy playback instance on unmount --- src/components/views/messages/MVoiceMessageBody.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/views/messages/MVoiceMessageBody.tsx b/src/components/views/messages/MVoiceMessageBody.tsx index 4a2a83465d..a6bd30ac6e 100644 --- a/src/components/views/messages/MVoiceMessageBody.tsx +++ b/src/components/views/messages/MVoiceMessageBody.tsx @@ -71,10 +71,16 @@ export default class MVoiceMessageBody extends React.PureComponent Date: Wed, 26 May 2021 13:07:57 +0530 Subject: [PATCH 098/159] Update src/components/views/messages/MVoiceMessageBody.tsx Co-authored-by: Michael Telatynski <7t3chguy@googlemail.com> --- src/components/views/messages/MVoiceMessageBody.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/views/messages/MVoiceMessageBody.tsx b/src/components/views/messages/MVoiceMessageBody.tsx index a6bd30ac6e..d65de7697a 100644 --- a/src/components/views/messages/MVoiceMessageBody.tsx +++ b/src/components/views/messages/MVoiceMessageBody.tsx @@ -76,9 +76,7 @@ export default class MVoiceMessageBody extends React.PureComponent Date: Wed, 26 May 2021 10:15:31 +0100 Subject: [PATCH 099/159] Fix preview generate check --- src/stores/room-list/MessagePreviewStore.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stores/room-list/MessagePreviewStore.ts b/src/stores/room-list/MessagePreviewStore.ts index 4de612c7bd..f5b9d9bc6a 100644 --- a/src/stores/room-list/MessagePreviewStore.ts +++ b/src/stores/room-list/MessagePreviewStore.ts @@ -176,7 +176,8 @@ export class MessagePreviewStore extends AsyncStoreWithClient { if (payload.action === 'MatrixActions.Room.timeline' || payload.action === 'MatrixActions.Event.decrypted') { const event = payload.event; // TODO: Type out the dispatcher - if (!this.previews.has(event.getRoomId()) || !event.isLiveEvent) return; // not important + const isHistoricalEvent = payload.hasOwnProperty("isLiveEvent") && !payload.isLiveEvent + if (!this.previews.has(event.getRoomId()) || isHistoricalEvent) return; // not important await this.generatePreview(this.matrixClient.getRoom(event.getRoomId()), TAG_ANY); } } From 3f10279e152c20b05569e53a9e60d7b65d62871d Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 26 May 2021 16:38:02 +0100 Subject: [PATCH 100/159] Invite Dialog don't show warning modals after unmount, it is jarring --- src/components/views/dialogs/InviteDialog.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index ec9c71ccbe..868d89bfa4 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -351,6 +351,7 @@ export default class InviteDialog extends React.PureComponent { this.setState({consultFirst: ev.target.checked}); } @@ -1027,6 +1032,7 @@ export default class InviteDialog extends React.PureComponent 0) { const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog'); From d4ca1babbe5228d028ebcc9ca1e89f66f1955337 Mon Sep 17 00:00:00 2001 From: "J. Ryan Stinnett" Date: Wed, 26 May 2021 16:47:21 +0100 Subject: [PATCH 101/159] Update reactions row on event decryption This fixes a race (perhaps revealed by the recent lazy decryption work) where the reactions row have reactions to show, but the event would not be decrypted, so they wouldn't render. Adding a decryption listener gets things moving again. Fixes https://github.com/vector-im/element-web/issues/17461 --- .../views/messages/ReactionsRow.tsx | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/src/components/views/messages/ReactionsRow.tsx b/src/components/views/messages/ReactionsRow.tsx index f5910473ff..797f997d27 100644 --- a/src/components/views/messages/ReactionsRow.tsx +++ b/src/components/views/messages/ReactionsRow.tsx @@ -81,18 +81,38 @@ export default class ReactionsRow extends React.PureComponent { constructor(props, context) { super(props, context); - if (props.reactions) { - props.reactions.on("Relations.add", this.onReactionsChange); - props.reactions.on("Relations.remove", this.onReactionsChange); - props.reactions.on("Relations.redaction", this.onReactionsChange); - } - this.state = { myReactions: this.getMyReactions(), showAll: false, }; } + componentDidMount() { + const { mxEvent, reactions } = this.props; + + if (mxEvent.isBeingDecrypted() || mxEvent.shouldAttemptDecryption()) { + mxEvent.once("Event.decrypted", this.onDecrypted); + } + + if (reactions) { + reactions.on("Relations.add", this.onReactionsChange); + reactions.on("Relations.remove", this.onReactionsChange); + reactions.on("Relations.redaction", this.onReactionsChange); + } + } + + componentWillUnmount() { + const { mxEvent, reactions } = this.props; + + mxEvent.off("Event.decrypted", this.onDecrypted); + + if (reactions) { + reactions.off("Relations.add", this.onReactionsChange); + reactions.off("Relations.remove", this.onReactionsChange); + reactions.off("Relations.redaction", this.onReactionsChange); + } + } + componentDidUpdate(prevProps) { if (prevProps.reactions !== this.props.reactions) { this.props.reactions.on("Relations.add", this.onReactionsChange); @@ -102,21 +122,9 @@ export default class ReactionsRow extends React.PureComponent { } } - componentWillUnmount() { - if (this.props.reactions) { - this.props.reactions.removeListener( - "Relations.add", - this.onReactionsChange, - ); - this.props.reactions.removeListener( - "Relations.remove", - this.onReactionsChange, - ); - this.props.reactions.removeListener( - "Relations.redaction", - this.onReactionsChange, - ); - } + private onDecrypted = () => { + // Decryption changes whether the event is actionable + this.forceUpdate(); } onReactionsChange = () => { From 60d161caf58e63f41650af9286bb03d08440ba65 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Wed, 26 May 2021 16:47:46 +0100 Subject: [PATCH 102/159] Apply some actual typescripting to this file --- src/components/views/dialogs/InviteDialog.tsx | 195 +++++++++--------- 1 file changed, 96 insertions(+), 99 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index 868d89bfa4..b9a5a68f83 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -47,6 +47,8 @@ import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call'; import {replaceableComponent} from "../../../utils/replaceableComponent"; import {mediaFromMxc} from "../../../customisations/Media"; import {getAddressType} from "../../../UserAddress"; +import BaseAvatar from '../avatars/BaseAvatar'; +import AccessibleButton from '../elements/AccessibleButton'; // we have a number of types defined from the Matrix spec which can't reasonably be altered here. /* eslint-disable camelcase */ @@ -61,43 +63,41 @@ const INCREMENT_ROOMS_SHOWN = 5; // Number of rooms to add when 'show more' is c // This is the interface that is expected by various components in this file. It is a bit // awkward because it also matches the RoomMember class from the js-sdk with some extra support // for 3PIDs/email addresses. -// -// XXX: We should use TypeScript interfaces instead of this weird "abstract" class. -class Member { +abstract class Member { /** * The display name of this Member. For users this should be their profile's display * name or user ID if none set. For 3PIDs this should be the 3PID address (email). */ - get name(): string { throw new Error("Member class not implemented"); } + public abstract get name(): string; /** * The ID of this Member. For users this should be their user ID. For 3PIDs this should * be the 3PID address (email). */ - get userId(): string { throw new Error("Member class not implemented"); } + public abstract get userId(): string; /** * Gets the MXC URL of this Member's avatar. For users this should be their profile's * avatar MXC URL or null if none set. For 3PIDs this should always be null. */ - getMxcAvatarUrl(): string { throw new Error("Member class not implemented"); } + public abstract getMxcAvatarUrl(): string; } class DirectoryMember extends Member { - _userId: string; - _displayName: string; - _avatarUrl: string; + private readonly _userId: string; + private readonly displayName: string; + private readonly avatarUrl: string; constructor(userDirResult: {user_id: string, display_name: string, avatar_url: string}) { super(); this._userId = userDirResult.user_id; - this._displayName = userDirResult.display_name; - this._avatarUrl = userDirResult.avatar_url; + this.displayName = userDirResult.display_name; + this.avatarUrl = userDirResult.avatar_url; } // These next class members are for the Member interface get name(): string { - return this._displayName || this._userId; + return this.displayName || this._userId; } get userId(): string { @@ -105,32 +105,32 @@ class DirectoryMember extends Member { } getMxcAvatarUrl(): string { - return this._avatarUrl; + return this.avatarUrl; } } class ThreepidMember extends Member { - _id: string; + private readonly id: string; constructor(id: string) { super(); - this._id = id; + this.id = id; } // This is a getter that would be falsey on all other implementations. Until we have // better type support in the react-sdk we can use this trick to determine the kind // of 3PID we're dealing with, if any. get isEmail(): boolean { - return this._id.includes('@'); + return this.id.includes('@'); } // These next class members are for the Member interface get name(): string { - return this._id; + return this.id; } get userId(): string { - return this._id; + return this.id; } getMxcAvatarUrl(): string { @@ -140,11 +140,11 @@ class ThreepidMember extends Member { interface IDMUserTileProps { member: RoomMember; - onRemove: (RoomMember) => any; + onRemove(member: RoomMember): void; } class DMUserTile extends React.PureComponent { - _onRemove = (e) => { + private onRemove = (e) => { // Stop the browser from highlighting text e.preventDefault(); e.stopPropagation(); @@ -153,9 +153,6 @@ class DMUserTile extends React.PureComponent { }; render() { - const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar"); - const AccessibleButton = sdk.getComponent("elements.AccessibleButton"); - const avatarSize = 20; const avatar = this.props.member.isEmail ? { closeButton = ( {_t('Remove')} { interface IDMRoomTileProps { member: RoomMember; lastActiveTs: number; - onToggle: (RoomMember) => any; + onToggle(member: RoomMember): void; highlightWord: string; isSelected: boolean; } class DMRoomTile extends React.PureComponent { - _onClick = (e) => { + private onClick = (e) => { // Stop the browser from highlighting text e.preventDefault(); e.stopPropagation(); @@ -215,7 +212,7 @@ class DMRoomTile extends React.PureComponent { this.props.onToggle(this.props.member); }; - _highlightName(str: string) { + private highlightName(str: string) { if (!this.props.highlightWord) return str; // We convert things to lowercase for index searching, but pull substrings from @@ -252,8 +249,6 @@ class DMRoomTile extends React.PureComponent { } render() { - const BaseAvatar = sdk.getComponent("views.avatars.BaseAvatar"); - let timestamp = null; if (this.props.lastActiveTs) { const humanTs = humanizeTime(this.props.lastActiveTs); @@ -291,13 +286,13 @@ class DMRoomTile extends React.PureComponent { const caption = this.props.member.isEmail ? _t("Invite by email") - : this._highlightName(this.props.member.userId); + : this.highlightName(this.props.member.userId); return ( -
    +
    {stackedAvatar} -
    {this._highlightName(this.props.member.name)}
    +
    {this.highlightName(this.props.member.name)}
    {caption}
    {timestamp} @@ -308,7 +303,7 @@ class DMRoomTile extends React.PureComponent { interface IInviteDialogProps { // Takes an array of user IDs/emails to invite. - onFinished: (toInvite?: string[]) => any; + onFinished: (toInvite?: string[]) => void; // The kind of invite being performed. Assumed to be KIND_DM if // not provided. @@ -349,8 +344,8 @@ export default class InviteDialog extends React.PureComponent(); private unmounted = false; constructor(props) { @@ -379,7 +374,7 @@ export default class InviteDialog extends React.PureComponent): {userId: string, user: RoomMember, lastActive: number}[] { + public static buildRecents(excludedTargetIds: Set): { + userId: string, + user: RoomMember, + lastActive: number, + }[] { const rooms = DMRoomMap.shared().getUniqueRoomsWithIndividuals(); // map of userId => js-sdk Room // Also pull in all the rooms tagged as DefaultTagID.DM so we don't miss anything. Sometimes the @@ -472,7 +469,7 @@ export default class InviteDialog extends React.PureComponent): {userId: string, user: RoomMember}[] { + private buildSuggestions(excludedTargetIds: Set): {userId: string, user: RoomMember}[] { const maxConsideredMembers = 200; const joinedRooms = MatrixClientPeg.get().getRooms() .filter(r => r.getMyMembership() === 'join' && r.getJoinedMemberCount() <= maxConsideredMembers); @@ -590,7 +587,7 @@ export default class InviteDialog extends React.PureComponent ({userId: m.member.userId, user: m.member})); } - _shouldAbortAfterInviteError(result): boolean { + private shouldAbortAfterInviteError(result): boolean { const failedUsers = Object.keys(result.states).filter(a => result.states[a] === 'error'); if (failedUsers.length > 0) { console.log("Failed to invite users: ", result); @@ -605,7 +602,7 @@ export default class InviteDialog extends React.PureComponent { + private startDm = async () => { this.setState({busy: true}); const client = MatrixClientPeg.get(); - const targets = this._convertFilter(); + const targets = this.convertFilter(); const targetIds = targets.map(t => t.userId); // Check if there is already a DM with these people and reuse it if possible. @@ -699,11 +696,11 @@ export default class InviteDialog extends React.PureComponent { + private inviteUsers = async () => { const startTime = CountlyAnalytics.getTimestamp(); this.setState({busy: true}); - this._convertFilter(); - const targets = this._convertFilter(); + this.convertFilter(); + const targets = this.convertFilter(); const targetIds = targets.map(t => t.userId); const cli = MatrixClientPeg.get(); @@ -720,7 +717,7 @@ export default class InviteDialog extends React.PureComponent { - this._convertFilter(); - const targets = this._convertFilter(); + private transferCall = async () => { + this.convertFilter(); + const targets = this.convertFilter(); const targetIds = targets.map(t => t.userId); if (targetIds.length > 1) { this.setState({ @@ -795,26 +792,26 @@ export default class InviteDialog extends React.PureComponent { + private onKeyDown = (e) => { if (this.state.busy) return; const value = e.target.value.trim(); const hasModifiers = e.ctrlKey || e.shiftKey || e.metaKey; if (!value && this.state.targets.length > 0 && e.key === Key.BACKSPACE && !hasModifiers) { // when the field is empty and the user hits backspace remove the right-most target e.preventDefault(); - this._removeMember(this.state.targets[this.state.targets.length - 1]); + this.removeMember(this.state.targets[this.state.targets.length - 1]); } else if (value && e.key === Key.ENTER && !hasModifiers) { // when the user hits enter with something in their field try to convert it e.preventDefault(); - this._convertFilter(); + this.convertFilter(); } else if (value && e.key === Key.SPACE && !hasModifiers && value.includes("@") && !value.includes(" ")) { // when the user hits space and their input looks like an e-mail/MXID then try to convert it e.preventDefault(); - this._convertFilter(); + this.convertFilter(); } }; - _updateSuggestions = async (term) => { + private updateSuggestions = async (term) => { MatrixClientPeg.get().searchUserDirectory({term}).then(async r => { if (term !== this.state.filterText) { // Discard the results - we were probably too slow on the server-side to make @@ -923,30 +920,30 @@ export default class InviteDialog extends React.PureComponent { + private updateFilter = (e) => { const term = e.target.value; this.setState({filterText: term}); // Debounce server lookups to reduce spam. We don't clear the existing server // results because they might still be vaguely accurate, likewise for races which // could happen here. - if (this._debounceTimer) { - clearTimeout(this._debounceTimer); + if (this.debounceTimer) { + clearTimeout(this.debounceTimer); } - this._debounceTimer = setTimeout(() => { - this._updateSuggestions(term); + this.debounceTimer = setTimeout(() => { + this.updateSuggestions(term); }, 150); // 150ms debounce (human reaction time + some) }; - _showMoreRecents = () => { + private showMoreRecents = () => { this.setState({numRecentsShown: this.state.numRecentsShown + INCREMENT_ROOMS_SHOWN}); }; - _showMoreSuggestions = () => { + private showMoreSuggestions = () => { this.setState({numSuggestionsShown: this.state.numSuggestionsShown + INCREMENT_ROOMS_SHOWN}); }; - _toggleMember = (member: Member) => { + private toggleMember = (member: Member) => { if (!this.state.busy) { let filterText = this.state.filterText; const targets = this.state.targets.map(t => t); // cheap clone for mutation @@ -959,13 +956,13 @@ export default class InviteDialog extends React.PureComponent { + private removeMember = (member: Member) => { const targets = this.state.targets.map(t => t); // cheap clone for mutation const idx = targets.indexOf(member); if (idx >= 0) { @@ -973,12 +970,12 @@ export default class InviteDialog extends React.PureComponent { + private onPaste = async (e) => { if (this.state.filterText) { // if the user has already typed something, just let them // paste normally. @@ -1049,17 +1046,17 @@ export default class InviteDialog extends React.PureComponent { + private onClickInputArea = (e) => { // Stop the browser from highlighting text e.preventDefault(); e.stopPropagation(); - if (this._editorRef && this._editorRef.current) { - this._editorRef.current.focus(); + if (this.editorRef && this.editorRef.current) { + this.editorRef.current.focus(); } }; - _onUseDefaultIdentityServerClick = (e) => { + private onUseDefaultIdentityServerClick = (e) => { e.preventDefault(); // Update the IS in account data. Actually using it may trigger terms. @@ -1068,21 +1065,21 @@ export default class InviteDialog extends React.PureComponent { + private onManageSettingsClick = (e) => { e.preventDefault(); dis.fire(Action.ViewUserSettings); this.props.onFinished(); }; - _onCommunityInviteClick = (e) => { + private onCommunityInviteClick = (e) => { this.props.onFinished(); showCommunityInviteDialog(CommunityPrototypeStore.instance.getSelectedCommunityId()); }; - _renderSection(kind: "recents"|"suggestions") { + private renderSection(kind: "recents"|"suggestions") { let sourceMembers = kind === 'recents' ? this.state.recents : this.state.suggestions; let showNum = kind === 'recents' ? this.state.numRecentsShown : this.state.numSuggestionsShown; - const showMoreFn = kind === 'recents' ? this._showMoreRecents.bind(this) : this._showMoreSuggestions.bind(this); + const showMoreFn = kind === 'recents' ? this.showMoreRecents.bind(this) : this.showMoreSuggestions.bind(this); const lastActive = (m) => kind === 'recents' ? m.lastActive : null; let sectionName = kind === 'recents' ? _t("Recent Conversations") : _t("Suggestions"); let sectionSubname = null; @@ -1162,7 +1159,7 @@ export default class InviteDialog extends React.PureComponent t.userId === r.userId)} /> @@ -1177,32 +1174,32 @@ export default class InviteDialog extends React.PureComponent ( - + )); const input = ( ); return ( -
    +
    {targets} {input}
    ); } - _renderIdentityServerWarning() { + private renderIdentityServerWarning() { if (!this.state.tryingIdentityServer || this.state.canUseIdentityServer || !SettingsStore.getValue(UIFeature.IdentityServer) ) { @@ -1220,8 +1217,8 @@ export default class InviteDialog extends React.PureComponent {sub}, - settings: sub => {sub}, + default: sub => {sub}, + settings: sub => {sub}, }, )}
    ); @@ -1231,7 +1228,7 @@ export default class InviteDialog extends React.PureComponentSettings.", {}, { - settings: sub => {sub}, + settings: sub => {sub}, }, )}
    ); @@ -1304,7 +1301,7 @@ export default class InviteDialog extends React.PureComponent{sub} ); }, @@ -1315,7 +1312,7 @@ export default class InviteDialog extends React.PureComponent; } buttonText = _t("Go"); - goButtonFn = this._startDm; + goButtonFn = this.startDm; } else if (this.props.kind === KIND_INVITE) { const room = MatrixClientPeg.get()?.getRoom(this.props.roomId); const isSpace = SettingsStore.getValue("feature_spaces") && room?.isSpaceRoom(); @@ -1354,7 +1351,7 @@ export default class InviteDialog extends React.PureComponent
    { { - setPanelCollapsed(!isPanelCollapsed); - if (menuDisplayed) closeMenu(); - }} + onClick={() => setPanelCollapsed(!isPanelCollapsed)} title={expandCollapseButtonTitle} /> { contextMenu } From be22a325f6d42b5d52b666462449b6aae30f8b2e Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 27 May 2021 08:57:27 +0100 Subject: [PATCH 105/159] Prevent having duplicates in pending room state --- src/components/structures/UserMenu.tsx | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index c05f74a436..32fd3aa471 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -69,7 +69,7 @@ interface IState { contextMenuPosition: PartialDOMRect; isDarkTheme: boolean; selectedSpace?: Room; - pendingRoomJoin: string[] + pendingRoomJoin: Set } @replaceableComponent("structures.UserMenu") @@ -86,7 +86,7 @@ export default class UserMenu extends React.Component { this.state = { contextMenuPosition: null, isDarkTheme: this.isUserOnDarkTheme(), - pendingRoomJoin: [], + pendingRoomJoin: new Set(), }; OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); @@ -168,28 +168,24 @@ export default class UserMenu extends React.Component { } }; - private addPendingJoinRoom(roomId) { + private addPendingJoinRoom(roomId: string): void { this.setState({ - pendingRoomJoin: [ - ...this.state.pendingRoomJoin, - roomId, - ], + pendingRoomJoin: new Set(this.state.pendingRoomJoin) + .add(roomId), }); } - private removePendingJoinRoom(roomId) { - const newPendingRoomJoin = this.state.pendingRoomJoin.filter(pendingJoinRoomId => { - return pendingJoinRoomId !== roomId; - }); - if (newPendingRoomJoin.length !== this.state.pendingRoomJoin.length) { + private removePendingJoinRoom(roomId: string): void { + if (this.state.pendingRoomJoin.has(roomId)) { + this.state.pendingRoomJoin.delete(roomId); this.setState({ - pendingRoomJoin: newPendingRoomJoin, + pendingRoomJoin: new Set(this.state.pendingRoomJoin), }) } } get hasPendingActions(): boolean { - return this.state.pendingRoomJoin.length > 0; + return Array.from(this.state.pendingRoomJoin).length > 0; } private onOpenMenuClick = (ev: React.MouseEvent) => { From 9007afabfa7b12c9b1358c9f9c4584b49b71c3f1 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 27 May 2021 08:57:48 +0100 Subject: [PATCH 106/159] Fix JoinRoomError action name typo --- src/dispatcher/actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dispatcher/actions.ts b/src/dispatcher/actions.ts index 0c9a4160b5..9fc0b54eea 100644 --- a/src/dispatcher/actions.ts +++ b/src/dispatcher/actions.ts @@ -152,5 +152,5 @@ export enum Action { /** * Fired when joining a room failed */ - JoinRoomError = "join_room", + JoinRoomError = "join_room_error", } From 2d15d66df81de03f502d1c32d83c623f38384041 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 27 May 2021 08:58:11 +0100 Subject: [PATCH 107/159] Listen to home server sync update to remove pending rooms --- src/components/structures/UserMenu.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 32fd3aa471..7543dff953 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -106,6 +106,7 @@ export default class UserMenu extends React.Component { this.dispatcherRef = defaultDispatcher.register(this.onAction); this.themeWatcherRef = SettingsStore.watchSetting("theme", null, this.onThemeChanged); this.tagStoreRef = GroupFilterOrderStore.addListener(this.onTagStoreUpdate); + MatrixClientPeg.get().on("Room", this.onRoom); } public componentWillUnmount() { @@ -117,6 +118,11 @@ export default class UserMenu extends React.Component { if (SettingsStore.getValue("feature_spaces")) { SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate); } + MatrixClientPeg.get().removeListener("Room", this.onRoom); + } + + private onRoom = (room: Room): void => { + this.removePendingJoinRoom(room.roomId); } private onTagStoreUpdate = () => { From fbb6a42d86b3f84fbe248752c80cf2129f6f2e28 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 27 May 2021 09:05:51 +0100 Subject: [PATCH 108/159] fix reading Set length --- src/components/structures/UserMenu.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 7543dff953..cc50db02ee 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -190,8 +190,8 @@ export default class UserMenu extends React.Component { } } - get hasPendingActions(): boolean { - return Array.from(this.state.pendingRoomJoin).length > 0; + get pendingActionsCount(): number { + return Array.from(this.state.pendingRoomJoin).length; } private onOpenMenuClick = (ev: React.MouseEvent) => { @@ -655,11 +655,11 @@ export default class UserMenu extends React.Component { /> {name} - {this.hasPendingActions && ( + {this.pendingActionsCount > 0 && ( )} From bd653ac5a89b1a47b7182c73bc5464f835645f07 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 27 May 2021 09:11:43 +0100 Subject: [PATCH 109/159] fix edge cases around space panel auto collapsing/closing menu --- src/components/views/spaces/SpacePanel.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/views/spaces/SpacePanel.tsx b/src/components/views/spaces/SpacePanel.tsx index aac1f609d5..eb63b21f0e 100644 --- a/src/components/views/spaces/SpacePanel.tsx +++ b/src/components/views/spaces/SpacePanel.tsx @@ -128,7 +128,9 @@ const SpacePanel = () => { const [isPanelCollapsed, setPanelCollapsed] = useState(true); useEffect(() => { - closeMenu(); + if (!isPanelCollapsed && menuDisplayed) { + closeMenu(); + } }, [isPanelCollapsed]); // eslint-disable-line react-hooks/exhaustive-deps const newClasses = classNames("mx_SpaceButton_new", { @@ -239,8 +241,8 @@ const SpacePanel = () => { className={newClasses} tooltip={menuDisplayed ? _t("Cancel") : _t("Create a space")} onClick={menuDisplayed ? closeMenu : () => { - openMenu(); if (!isPanelCollapsed) setPanelCollapsed(true); + openMenu(); }} isNarrow={isPanelCollapsed} /> From b8a7d5d730094f5442d063ebfdb9bff6df38dcb3 Mon Sep 17 00:00:00 2001 From: Germain Date: Thu, 27 May 2021 09:23:56 +0100 Subject: [PATCH 110/159] Better Set handling Co-authored-by: Michael Telatynski <7t3chguy@gmail.com> --- src/components/structures/UserMenu.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index cc50db02ee..ff00905a59 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -69,7 +69,7 @@ interface IState { contextMenuPosition: PartialDOMRect; isDarkTheme: boolean; selectedSpace?: Room; - pendingRoomJoin: Set + pendingRoomJoin: Set; } @replaceableComponent("structures.UserMenu") @@ -182,8 +182,7 @@ export default class UserMenu extends React.Component { } private removePendingJoinRoom(roomId: string): void { - if (this.state.pendingRoomJoin.has(roomId)) { - this.state.pendingRoomJoin.delete(roomId); + if (this.state.pendingRoomJoin.delete(roomId)) { this.setState({ pendingRoomJoin: new Set(this.state.pendingRoomJoin), }) From f31ec343f4b587d9fb928653418ccbb03e00158c Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 27 May 2021 09:26:26 +0100 Subject: [PATCH 111/159] Use Set::size instead of Array.from()::length --- src/components/structures/UserMenu.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index ff00905a59..fb4829f879 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -189,10 +189,6 @@ export default class UserMenu extends React.Component { } } - get pendingActionsCount(): number { - return Array.from(this.state.pendingRoomJoin).length; - } - private onOpenMenuClick = (ev: React.MouseEvent) => { ev.preventDefault(); ev.stopPropagation(); @@ -654,11 +650,11 @@ export default class UserMenu extends React.Component { /> {name} - {this.pendingActionsCount > 0 && ( + {this.state.pendingRoomJoin.size > 0 && ( )} From d6d09227530da472ba5ecd9de99c32891ed9e82c Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Thu, 27 May 2021 10:11:28 +0100 Subject: [PATCH 112/159] Fix misleading child counts in spaces --- src/components/structures/SpaceRoomDirectory.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx index 3985553b20..8d59fe6c68 100644 --- a/src/components/structures/SpaceRoomDirectory.tsx +++ b/src/components/structures/SpaceRoomDirectory.tsx @@ -319,7 +319,7 @@ export const HierarchyLevel = ({ key={roomId} room={rooms.get(roomId)} numChildRooms={Array.from(relations.get(roomId)?.values() || []) - .filter(ev => rooms.get(ev.state_key)?.room_type !== RoomType.Space).length} + .filter(ev => rooms.has(ev.state_key) && !rooms.get(ev.state_key).room_type).length} suggested={relations.get(spaceId)?.get(roomId)?.content.suggested} selected={selectedMap?.get(spaceId)?.has(roomId)} onViewRoomClick={(autoJoin) => { @@ -437,7 +437,7 @@ export const SpaceHierarchy: React.FC = ({ let content; if (roomsMap) { - const numRooms = Array.from(roomsMap.values()).filter(r => r.room_type !== RoomType.Space).length; + const numRooms = Array.from(roomsMap.values()).filter(r => !r.room_type).length; const numSpaces = roomsMap.size - numRooms - 1; // -1 at the end to exclude the space we are looking at let countsStr; From fcae19f8318a3bf0bd8908162e56bd0b3d2f3884 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 27 May 2021 12:36:16 +0100 Subject: [PATCH 113/159] Track left panel width using ResizeObserver --- src/@types/global.d.ts | 2 + src/components/structures/LeftPanel.tsx | 8 +++- src/stores/UIStore.ts | 53 +++++++++++++++++++++---- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 63966d96fa..22280b8a28 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -43,6 +43,7 @@ import TypingStore from "../stores/TypingStore"; import { EventIndexPeg } from "../indexing/EventIndexPeg"; import {VoiceRecordingStore} from "../stores/VoiceRecordingStore"; import PerformanceMonitor from "../performance"; +import UIStore from "../stores/UIStore"; declare global { interface Window { @@ -82,6 +83,7 @@ declare global { mxEventIndexPeg: EventIndexPeg; mxPerformanceMonitor: PerformanceMonitor; mxPerformanceEntryNames: any; + mxUIStore: UIStore; } interface Document { diff --git a/src/components/structures/LeftPanel.tsx b/src/components/structures/LeftPanel.tsx index 22c60bff1e..80cd9bc465 100644 --- a/src/components/structures/LeftPanel.tsx +++ b/src/components/structures/LeftPanel.tsx @@ -67,6 +67,7 @@ const cssClasses = [ @replaceableComponent("structures.LeftPanel") export default class LeftPanel extends React.Component { + private ref: React.RefObject = createRef(); private listContainerRef: React.RefObject = createRef(); private groupFilterPanelWatcherRef: string; private bgImageWatcherRef: string; @@ -93,6 +94,10 @@ export default class LeftPanel extends React.Component { }); } + public componentDidMount() { + UIStore.instance.trackElementDimensions("LeftPanel", this.ref.current); + } + public componentWillUnmount() { SettingsStore.unwatchSetting(this.groupFilterPanelWatcherRef); SettingsStore.unwatchSetting(this.bgImageWatcherRef); @@ -100,6 +105,7 @@ export default class LeftPanel extends React.Component { RoomListStore.instance.off(LISTS_UPDATE_EVENT, this.onBreadcrumbsUpdate); OwnProfileStore.instance.off(UPDATE_EVENT, this.onBackgroundImageUpdate); SpaceStore.instance.off(UPDATE_SELECTED_SPACE, this.updateActiveSpace); + UIStore.instance.stopTrackingElementDimensions("LeftPanel"); } private updateActiveSpace = (activeSpace: Room) => { @@ -420,7 +426,7 @@ export default class LeftPanel extends React.Component { ); return ( -
    +
    {leftLeftPanel}