diff --git a/CHANGELOG.md b/CHANGELOG.md
index d459b4e94a..58d23e3413 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,115 @@
+Changes in [3.20.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.20.0) (2021-05-10)
+=====================================================================================================
+[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.20.0-rc.1...v3.20.0)
+
+ * Upgrade to JS SDK 10.1.0
+ * [Release] Don't use the event's metadata to calc the scale of an image
+ [\#6004](https://github.com/matrix-org/matrix-react-sdk/pull/6004)
+
+Changes in [3.20.0-rc.1](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.20.0-rc.1) (2021-05-04)
+===============================================================================================================
+[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.19.0...v3.20.0-rc.1)
+
+ * Upgrade to JS SDK 10.1.0-rc.1
+ * Translations update from Weblate
+ [\#5966](https://github.com/matrix-org/matrix-react-sdk/pull/5966)
+ * Fix more space panel layout and hover behaviour issues
+ [\#5965](https://github.com/matrix-org/matrix-react-sdk/pull/5965)
+ * Fix edge case with space panel alignment with subspaces on ff
+ [\#5964](https://github.com/matrix-org/matrix-react-sdk/pull/5964)
+ * Fix saving room pill part to history
+ [\#5951](https://github.com/matrix-org/matrix-react-sdk/pull/5951)
+ * Generate room preview even when minimized
+ [\#5948](https://github.com/matrix-org/matrix-react-sdk/pull/5948)
+ * Another change from recovery passphrase to Security Phrase
+ [\#5934](https://github.com/matrix-org/matrix-react-sdk/pull/5934)
+ * Sort rooms in the add existing to space dialog based on recency
+ [\#5943](https://github.com/matrix-org/matrix-react-sdk/pull/5943)
+ * Inhibit sending RR when context switching to a room
+ [\#5944](https://github.com/matrix-org/matrix-react-sdk/pull/5944)
+ * Prevent room list keyboard handling from landing focus on hidden nodes
+ [\#5950](https://github.com/matrix-org/matrix-react-sdk/pull/5950)
+ * Make the text filter search all spaces instead of just the selected one
+ [\#5942](https://github.com/matrix-org/matrix-react-sdk/pull/5942)
+ * Enable indent rule and fix indent
+ [\#5931](https://github.com/matrix-org/matrix-react-sdk/pull/5931)
+ * Prevent peeking members from reacting
+ [\#5946](https://github.com/matrix-org/matrix-react-sdk/pull/5946)
+ * Disallow inline display maths
+ [\#5939](https://github.com/matrix-org/matrix-react-sdk/pull/5939)
+ * Space creation prompt user to add existing rooms for "Just Me" spaces
+ [\#5923](https://github.com/matrix-org/matrix-react-sdk/pull/5923)
+ * Add test coverage collection script
+ [\#5937](https://github.com/matrix-org/matrix-react-sdk/pull/5937)
+ * Fix joining room using via servers regression
+ [\#5936](https://github.com/matrix-org/matrix-react-sdk/pull/5936)
+ * Revert "Fixes the two Todays problem in Redaction"
+ [\#5938](https://github.com/matrix-org/matrix-react-sdk/pull/5938)
+ * Handle encoded matrix URLs
+ [\#5903](https://github.com/matrix-org/matrix-react-sdk/pull/5903)
+ * Render ignored users setting regardless of if there are any
+ [\#5860](https://github.com/matrix-org/matrix-react-sdk/pull/5860)
+ * Fix inserting trailing colon after mention/pill
+ [\#5830](https://github.com/matrix-org/matrix-react-sdk/pull/5830)
+ * Fixes the two Todays problem in Redaction
+ [\#5917](https://github.com/matrix-org/matrix-react-sdk/pull/5917)
+ * Fix page up/down scrolling only half a page
+ [\#5920](https://github.com/matrix-org/matrix-react-sdk/pull/5920)
+ * Voice messages: Composer controls
+ [\#5935](https://github.com/matrix-org/matrix-react-sdk/pull/5935)
+ * Support MSC3086 asserted identity
+ [\#5886](https://github.com/matrix-org/matrix-react-sdk/pull/5886)
+ * Handle possible edge case with getting stuck in "unsent messages" bar
+ [\#5930](https://github.com/matrix-org/matrix-react-sdk/pull/5930)
+ * Fix suggested rooms not showing up regression from room list optimisation
+ [\#5932](https://github.com/matrix-org/matrix-react-sdk/pull/5932)
+ * Broadcast language change to ElectronPlatform
+ [\#5913](https://github.com/matrix-org/matrix-react-sdk/pull/5913)
+ * Fix VoIP PIP frame color
+ [\#5701](https://github.com/matrix-org/matrix-react-sdk/pull/5701)
+ * Convert some Flow-typed files to TypeScript
+ [\#5912](https://github.com/matrix-org/matrix-react-sdk/pull/5912)
+ * Initial SpaceStore tests work
+ [\#5906](https://github.com/matrix-org/matrix-react-sdk/pull/5906)
+ * Fix issues with space hierarchy in layout and with incompatible servers
+ [\#5926](https://github.com/matrix-org/matrix-react-sdk/pull/5926)
+ * Scale all mxc thumbs using device pixel ratio for hidpi
+ [\#5928](https://github.com/matrix-org/matrix-react-sdk/pull/5928)
+ * Fix add existing to space dialog no longer showing rooms for public spaces
+ [\#5918](https://github.com/matrix-org/matrix-react-sdk/pull/5918)
+ * Disable spaces context switching for when exploring a space
+ [\#5924](https://github.com/matrix-org/matrix-react-sdk/pull/5924)
+ * Autofocus search box in the add existing to space dialog
+ [\#5921](https://github.com/matrix-org/matrix-react-sdk/pull/5921)
+ * Use label element in add existing to space dialog for easier hit target
+ [\#5922](https://github.com/matrix-org/matrix-react-sdk/pull/5922)
+ * Dynamic max and min zoom in the new ImageView
+ [\#5916](https://github.com/matrix-org/matrix-react-sdk/pull/5916)
+ * Improve message error states
+ [\#5897](https://github.com/matrix-org/matrix-react-sdk/pull/5897)
+ * Check for null room in `VisibilityProvider`
+ [\#5914](https://github.com/matrix-org/matrix-react-sdk/pull/5914)
+ * Add unit tests for various collection-based utility functions
+ [\#5910](https://github.com/matrix-org/matrix-react-sdk/pull/5910)
+ * Spaces visual fixes
+ [\#5909](https://github.com/matrix-org/matrix-react-sdk/pull/5909)
+ * Remove reliance on DOM API to generated message preview
+ [\#5908](https://github.com/matrix-org/matrix-react-sdk/pull/5908)
+ * Expand upon voice message event & include overall waveform
+ [\#5888](https://github.com/matrix-org/matrix-react-sdk/pull/5888)
+ * Use floats for image background opacity
+ [\#5905](https://github.com/matrix-org/matrix-react-sdk/pull/5905)
+ * Show invites to spaces at the top of the space panel
+ [\#5902](https://github.com/matrix-org/matrix-react-sdk/pull/5902)
+ * Improve edge cases with spaces context switching
+ [\#5899](https://github.com/matrix-org/matrix-react-sdk/pull/5899)
+ * Fix spaces notification dots wrongly including upgraded (hidden) rooms
+ [\#5900](https://github.com/matrix-org/matrix-react-sdk/pull/5900)
+ * Iterate the spaces face pile design
+ [\#5898](https://github.com/matrix-org/matrix-react-sdk/pull/5898)
+ * Fix alignment issue with nested spaces being cut off wrong
+ [\#5890](https://github.com/matrix-org/matrix-react-sdk/pull/5890)
+
Changes in [3.19.0](https://github.com/matrix-org/matrix-react-sdk/releases/tag/v3.19.0) (2021-04-26)
=====================================================================================================
[Full Changelog](https://github.com/matrix-org/matrix-react-sdk/compare/v3.19.0-rc.1...v3.19.0)
diff --git a/README.md b/README.md
index 73afe34df0..b3e96ef001 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ Platform Targets:
* WebRTC features (VoIP and Video calling) are only available in Chrome & Firefox.
* Mobile Web is not currently a target platform - instead please use the native
iOS (https://github.com/matrix-org/matrix-ios-kit) and Android
- (https://github.com/matrix-org/matrix-android-sdk) SDKs.
+ (https://github.com/matrix-org/matrix-android-sdk2) SDKs.
All code lands on the `develop` branch - `master` is only used for stable releases.
**Please file PRs against `develop`!!**
diff --git a/package.json b/package.json
index e54de8a96c..342591081f 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "matrix-react-sdk",
- "version": "3.19.0",
+ "version": "3.20.0",
"description": "SDK for matrix.org using React",
"author": "matrix.org",
"repository": {
@@ -97,7 +97,7 @@
"react-transition-group": "^4.4.1",
"resize-observer-polyfill": "^1.5.1",
"rfc4648": "^1.4.0",
- "sanitize-html": "github:apostrophecms/sanitize-html#3c7f93f2058f696f5359e3e58d464161647226db",
+ "sanitize-html": "^2.3.2",
"tar-js": "^0.3.0",
"text-encoding-utf-8": "^1.0.2",
"url": "^0.11.0",
@@ -132,6 +132,7 @@
"@types/modernizr": "^3.5.3",
"@types/node": "^14.14.22",
"@types/pako": "^1.0.1",
+ "@types/parse5": "^6.0.0",
"@types/qrcode": "^1.3.5",
"@types/react": "^16.9",
"@types/react-dom": "^16.9.10",
diff --git a/res/css/structures/_SpacePanel.scss b/res/css/structures/_SpacePanel.scss
index 9264e82ea5..c433ccf275 100644
--- a/res/css/structures/_SpacePanel.scss
+++ b/res/css/structures/_SpacePanel.scss
@@ -237,7 +237,6 @@ $activeBorderColor: $secondary-fg-color;
.mx_SpacePanel_badgeContainer {
position: absolute;
- height: 16px;
// Create a flexbox to make aligning dot badges easier
display: flex;
@@ -249,23 +248,37 @@ $activeBorderColor: $secondary-fg-color;
.mx_NotificationBadge_dot {
// make the smaller dot occupy the same width for centering
- margin-left: 7px;
- margin-right: 7px;
+ margin: 0 7px;
}
}
&.collapsed {
.mx_SpaceButton {
.mx_SpacePanel_badgeContainer {
- right: -3px;
- top: -3px;
+ right: 0;
+ top: 0;
+
+ .mx_NotificationBadge {
+ background-clip: padding-box;
+ }
+
+ .mx_NotificationBadge_dot {
+ margin: 0 -1px 0 0;
+ border: 3px solid $groupFilterPanel-bg-color;
+ }
+
+ .mx_NotificationBadge_2char,
+ .mx_NotificationBadge_3char {
+ margin: -5px -5px 0 0;
+ border: 2px solid $groupFilterPanel-bg-color;
+ }
}
&.mx_SpaceButton_active .mx_SpacePanel_badgeContainer {
// when we draw the selection border we move the relative bounds of our parent
// so update our position within the bounds of the parent to maintain position overall
- right: -6px;
- top: -6px;
+ right: -3px;
+ top: -3px;
}
}
}
diff --git a/res/css/structures/_SpaceRoomDirectory.scss b/res/css/structures/_SpaceRoomDirectory.scss
index c7d087d8e0..7925686bf1 100644
--- a/res/css/structures/_SpaceRoomDirectory.scss
+++ b/res/css/structures/_SpaceRoomDirectory.scss
@@ -86,7 +86,7 @@ limitations under the License.
color: $primary-fg-color;
.mx_AccessibleButton {
- padding: 2px 8px;
+ padding: 4px 12px;
font-weight: normal;
& + .mx_AccessibleButton {
@@ -94,6 +94,11 @@ limitations under the License.
}
}
+ .mx_AccessibleButton_kind_danger_outline,
+ .mx_AccessibleButton_kind_primary_outline {
+ padding: 3px 12px; // to account for the 1px border
+ }
+
> span {
margin-left: auto;
}
@@ -246,11 +251,17 @@ limitations under the License.
grid-row: 1/3;
.mx_AccessibleButton {
- padding: 8px 18px;
+ line-height: $font-24px;
+ padding: 4px 16px;
display: inline-block;
visibility: hidden;
}
+ .mx_AccessibleButton_kind_danger_outline,
+ .mx_AccessibleButton_kind_primary_outline {
+ padding: 3px 16px; // to account for the 1px border
+ }
+
.mx_Checkbox {
display: inline-flex;
vertical-align: middle;
diff --git a/res/css/structures/_SpaceRoomView.scss b/res/css/structures/_SpaceRoomView.scss
index 553919d862..ff51e28b7b 100644
--- a/res/css/structures/_SpaceRoomView.scss
+++ b/res/css/structures/_SpaceRoomView.scss
@@ -238,7 +238,8 @@ $SpaceRoomViewInnerWidth: 428px;
.mx_SpaceRoomView_landing_inviteButton {
position: relative;
- padding-left: 40px;
+ padding: 4px 18px 4px 40px;
+ line-height: $font-24px;
height: min-content;
&::before {
@@ -254,6 +255,27 @@ $SpaceRoomViewInnerWidth: 428px;
mask-image: url('$(res)/img/element-icons/room/invite.svg');
}
}
+
+ .mx_SpaceRoomView_landing_settingsButton {
+ position: relative;
+ margin-left: 16px;
+ width: 24px;
+ height: 24px;
+
+ &::before {
+ position: absolute;
+ content: "";
+ left: 0;
+ top: 0;
+ height: 24px;
+ width: 24px;
+ background: $tertiary-fg-color;
+ mask-position: center;
+ mask-size: contain;
+ mask-repeat: no-repeat;
+ mask-image: url('$(res)/img/element-icons/settings.svg');
+ }
+ }
}
.mx_SpaceRoomView_landing_topic {
@@ -268,80 +290,6 @@ $SpaceRoomViewInnerWidth: 428px;
background-color: $groupFilterPanel-bg-color;
}
- .mx_SpaceRoomView_landing_adminButtons {
- margin-top: 24px;
-
- .mx_AccessibleButton {
- position: relative;
- width: 160px;
- height: 124px;
- box-sizing: border-box;
- padding: 72px 16px 0;
- border-radius: 12px;
- border: 1px solid $input-border-color;
- margin-right: 28px;
- margin-bottom: 20px;
- font-size: $font-14px;
- display: inline-block;
- vertical-align: bottom;
-
- &:last-child {
- margin-right: 0;
- }
-
- &:hover {
- background-color: rgba(141, 151, 165, 0.1);
- }
-
- &::before, &::after {
- position: absolute;
- content: "";
- left: 16px;
- top: 16px;
- height: 40px;
- width: 40px;
- border-radius: 20px;
- }
-
- &::after {
- mask-position: center;
- mask-size: 30px;
- mask-repeat: no-repeat;
- background: #ffffff; // white icon fill
- }
-
- &.mx_SpaceRoomView_landing_addButton {
- &::before {
- background-color: #ac3ba8;
- }
-
- &::after {
- mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
- }
- }
-
- &.mx_SpaceRoomView_landing_createButton {
- &::before {
- background-color: #368bd6;
- }
-
- &::after {
- mask-image: url('$(res)/img/element-icons/roomlist/explore.svg');
- }
- }
-
- &.mx_SpaceRoomView_landing_settingsButton {
- &::before {
- background-color: #5c56f5;
- }
-
- &::after {
- mask-image: url('$(res)/img/element-icons/settings.svg');
- }
- }
- }
- }
-
.mx_SearchBox {
margin: 0 0 20px;
}
diff --git a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss
index 84f965e6bd..524f107165 100644
--- a/res/css/views/dialogs/_AddExistingToSpaceDialog.scss
+++ b/res/css/views/dialogs/_AddExistingToSpaceDialog.scss
@@ -101,7 +101,7 @@ limitations under the License.
.mx_BaseAvatar {
display: inline-flex;
- margin: 5px 16px 5px 5px;
+ margin: auto 16px auto 5px;
vertical-align: middle;
}
@@ -160,31 +160,32 @@ limitations under the License.
}
}
- .mx_AddExistingToSpaceDialog_errorText {
- font-weight: $font-semi-bold;
- font-size: $font-12px;
- line-height: $font-15px;
- color: $notice-primary-color;
- margin-bottom: 28px;
- }
-
.mx_AddExistingToSpace {
display: contents;
}
.mx_AddExistingToSpaceDialog_footer {
display: flex;
- margin-top: 32px;
+ margin-top: 20px;
> span {
flex-grow: 1;
- font-size: $font-14px;
+ font-size: $font-12px;
line-height: $font-15px;
- font-weight: $font-semi-bold;
+ color: $secondary-fg-color;
- .mx_AccessibleButton {
- font-size: inherit;
- display: inline-block;
+ .mx_ProgressBar {
+ height: 8px;
+ width: 100%;
+
+ @mixin ProgressBarBorderRadius 8px;
+ }
+
+ .mx_AddExistingToSpaceDialog_progressText {
+ margin-top: 8px;
+ font-size: $font-15px;
+ line-height: $font-24px;
+ color: $primary-fg-color;
}
> * {
@@ -192,8 +193,54 @@ limitations under the License.
}
}
+ .mx_AddExistingToSpaceDialog_error {
+ padding-left: 12px;
+
+ > img {
+ align-self: center;
+ }
+
+ .mx_AddExistingToSpaceDialog_errorHeading {
+ font-weight: $font-semi-bold;
+ font-size: $font-15px;
+ line-height: $font-18px;
+ color: $notice-primary-color;
+ }
+
+ .mx_AddExistingToSpaceDialog_errorCaption {
+ margin-top: 4px;
+ font-size: $font-12px;
+ line-height: $font-15px;
+ color: $primary-fg-color;
+ }
+ }
+
.mx_AccessibleButton {
display: inline-block;
+ align-self: center;
+ }
+
+ .mx_AccessibleButton_kind_primary {
+ padding: 8px 36px;
+ }
+
+ .mx_AddExistingToSpaceDialog_retryButton {
+ margin-left: 12px;
+ padding-left: 24px;
+ position: relative;
+
+ &::before {
+ content: '';
+ position: absolute;
+ background-color: $primary-fg-color;
+ mask-repeat: no-repeat;
+ mask-position: center;
+ mask-size: contain;
+ mask-image: url('$(res)/img/element-icons/retry.svg');
+ width: 18px;
+ height: 18px;
+ left: 0;
+ }
}
.mx_AccessibleButton_kind_link {
diff --git a/res/css/views/elements/_AccessibleButton.scss b/res/css/views/elements/_AccessibleButton.scss
index 0075dcb511..2997c83cfd 100644
--- a/res/css/views/elements/_AccessibleButton.scss
+++ b/res/css/views/elements/_AccessibleButton.scss
@@ -76,12 +76,16 @@ limitations under the License.
border: 1px solid $button-danger-bg-color;
}
-.mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled,
-.mx_AccessibleButton_kind_danger_outline.mx_AccessibleButton_disabled {
+.mx_AccessibleButton_kind_danger.mx_AccessibleButton_disabled {
color: $button-danger-disabled-fg-color;
background-color: $button-danger-disabled-bg-color;
}
+.mx_AccessibleButton_kind_danger_outline.mx_AccessibleButton_disabled {
+ color: $button-danger-disabled-bg-color;
+ border-color: $button-danger-disabled-bg-color;
+}
+
.mx_AccessibleButton_hasKind.mx_AccessibleButton_kind_danger_sm {
padding: 5px 12px;
color: $button-danger-fg-color;
diff --git a/res/css/views/elements/_ProgressBar.scss b/res/css/views/elements/_ProgressBar.scss
index 770978e921..c075ac74ff 100644
--- a/res/css/views/elements/_ProgressBar.scss
+++ b/res/css/views/elements/_ProgressBar.scss
@@ -21,7 +21,7 @@ progress.mx_ProgressBar {
appearance: none;
border: none;
- @mixin ProgressBarBorderRadius "6px";
+ @mixin ProgressBarBorderRadius 6px;
@mixin ProgressBarColour $progressbar-fg-color;
@mixin ProgressBarBgColour $progressbar-bg-color;
::-webkit-progress-value {
diff --git a/res/css/views/messages/_MFileBody.scss b/res/css/views/messages/_MFileBody.scss
index b45126acf8..c215d69ec2 100644
--- a/res/css/views/messages/_MFileBody.scss
+++ b/res/css/views/messages/_MFileBody.scss
@@ -61,9 +61,9 @@ limitations under the License.
.mx_MFileBody_info {
background-color: $message-body-panel-bg-color;
- border-radius: 4px;
- width: 270px;
- padding: 8px;
+ border-radius: 12px;
+ width: 243px; // same width as a playable voice message, accounting for padding
+ padding: 6px 12px;
color: $message-body-panel-fg-color;
.mx_MFileBody_info_icon {
@@ -82,7 +82,7 @@ limitations under the License.
mask-position: center;
mask-size: cover;
mask-image: url('$(res)/img/element-icons/room/composer/attach.svg');
- background-color: $message-body-panel-fg-color;
+ background-color: $message-body-panel-icon-fg-color;
width: 13px;
height: 15px;
diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.scss b/res/css/views/rooms/_VoiceRecordComposerTile.scss
index 15daf81672..a3ee104bd8 100644
--- a/res/css/views/rooms/_VoiceRecordComposerTile.scss
+++ b/res/css/views/rooms/_VoiceRecordComposerTile.scss
@@ -39,23 +39,25 @@ limitations under the License.
width: 14px; // w&h are size of icon
height: 18px;
vertical-align: middle;
- margin-right: 7px; // distance from left edge of waveform container (container has some margin too)
+ margin-right: 11px; // distance from left edge of waveform container (container has some margin too)
background-color: $voice-record-icon-color;
mask-repeat: no-repeat;
mask-size: contain;
mask-image: url('$(res)/img/element-icons/trashcan.svg');
}
-.mx_VoiceMessagePrimaryContainer {
+.mx_MessageComposer_row .mx_VoiceMessagePrimaryContainer {
// Note: remaining class properties are in the PlayerContainer CSS.
margin: 6px; // force the composer area to put a gutter around us
- margin-right: 12px; // isolate from stop button
+ margin-right: 12px; // isolate from stop/send button
position: relative; // important for the live circle
&.mx_VoiceRecordComposerTile_recording {
- padding-left: 16px; // +10px for the live circle, +6px for regular padding
+ // We are putting the circle in this padding, so we need +10px from the regular
+ // padding on the left side.
+ padding-left: 22px;
&::before {
animation: recording-pulse 2s infinite;
@@ -65,8 +67,8 @@ limitations under the License.
width: 10px;
height: 10px;
position: absolute;
- left: 8px;
- top: 16px; // vertically center
+ left: 12px; // 12px from the left edge for container padding
+ top: 18px; // vertically center (middle align with clock)
border-radius: 10px;
}
}
diff --git a/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss
index 109edfff81..0f879d209e 100644
--- a/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss
+++ b/res/css/views/settings/tabs/user/_HelpUserSettingsTab.scss
@@ -22,3 +22,34 @@ limitations under the License.
.mx_HelpUserSettingsTab span.mx_AccessibleButton {
word-break: break-word;
}
+
+.mx_HelpUserSettingsTab code {
+ word-break: break-all;
+ user-select: all;
+}
+
+.mx_HelpUserSettingsTab_accessToken {
+ display: flex;
+ justify-content: space-between;
+ border-radius: 5px;
+ border: solid 1px $light-fg-color;
+ margin-bottom: 10px;
+ margin-top: 10px;
+ padding: 10px;
+}
+
+.mx_HelpUserSettingsTab_accessToken_copy {
+ flex-shrink: 0;
+ cursor: pointer;
+ margin-left: 20px;
+ display: inherit;
+}
+
+.mx_HelpUserSettingsTab_accessToken_copy > div {
+ mask-image: url($copy-button-url);
+ background-color: $message-action-bar-fg-color;
+ margin-left: 5px;
+ width: 20px;
+ height: 20px;
+ background-repeat: no-repeat;
+}
diff --git a/res/css/views/voice_messages/_PlaybackContainer.scss b/res/css/views/voice_messages/_PlaybackContainer.scss
index 49bd81ef81..64e8f445e1 100644
--- a/res/css/views/voice_messages/_PlaybackContainer.scss
+++ b/res/css/views/voice_messages/_PlaybackContainer.scss
@@ -19,8 +19,9 @@ limitations under the License.
// Container for live recording and playback controls
.mx_VoiceMessagePrimaryContainer {
- padding: 6px; // makes us 4px taller than the send/stop button
- padding-right: 5px; // there's 1px from the waveform itself, so account for that
+ // 7px top and bottom for visual design. 12px left & right, but the waveform (right)
+ // has a 1px padding on it that we want to account for.
+ padding: 7px 12px 7px 11px;
background-color: $voice-record-waveform-bg-color;
border-radius: 12px;
@@ -30,11 +31,9 @@ limitations under the License.
color: $voice-record-waveform-fg-color;
font-size: $font-14px;
+ line-height: $font-24px;
.mx_Waveform {
- // We want the bars to be 2px shorter than the play/pause button in the waveform control
- height: 28px; // default is 30px, so we're subtracting the 2px border off the bars
-
.mx_Waveform_bar {
background-color: $voice-record-waveform-incomplete-fg-color;
@@ -47,8 +46,8 @@ limitations under the License.
}
.mx_Clock {
- padding-right: 4px; // isolate from waveform
- padding-left: 8px; // isolate from live circle
- width: 40px; // we're not using a monospace font, so fake it
+ width: 42px; // we're not using a monospace font, so fake it
+ padding-right: 6px; // with the fixed width this ends up as a visual 8px most of the time, as intended.
+ padding-left: 8px; // isolate from recording circle / play control
}
}
diff --git a/res/css/views/voip/_CallView.scss b/res/css/views/voip/_CallView.scss
index 7292e325df..0be75be28c 100644
--- a/res/css/views/voip/_CallView.scss
+++ b/res/css/views/voip/_CallView.scss
@@ -65,14 +65,17 @@ limitations under the License.
}
}
-.mx_CallView_voice {
+.mx_CallView_content {
position: relative;
display: flex;
- flex-direction: column;
+ border-radius: 8px;
+}
+
+.mx_CallView_voice {
align-items: center;
justify-content: center;
+ flex-direction: column;
background-color: $inverted-bg-color;
- border-radius: 8px;
}
.mx_CallView_voice_avatarsContainer {
@@ -109,9 +112,7 @@ limitations under the License.
.mx_CallView_video {
width: 100%;
height: 100%;
- position: relative;
z-index: 30;
- border-radius: 8px;
overflow: hidden;
}
diff --git a/res/css/views/voip/_VideoFeed.scss b/res/css/views/voip/_VideoFeed.scss
index 8ead8bba3e..7d85ac264e 100644
--- a/res/css/views/voip/_VideoFeed.scss
+++ b/res/css/views/voip/_VideoFeed.scss
@@ -14,21 +14,37 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
+.mx_VideoFeed_voice {
+ // We don't want to collide with the call controls that have 52px of height
+ padding-bottom: 52px;
+ background-color: $inverted-bg-color;
+}
+
+
.mx_VideoFeed_remote {
width: 100%;
height: 100%;
- background-color: #000;
- z-index: 50;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ &.mx_VideoFeed_video {
+ background-color: #000;
+ }
}
.mx_VideoFeed_local {
- width: 25%;
- height: 25%;
+ max-width: 25%;
+ max-height: 25%;
position: absolute;
right: 10px;
top: 10px;
z-index: 100;
border-radius: 4px;
+
+ &.mx_VideoFeed_video {
+ background-color: transparent;
+ }
}
.mx_VideoFeed_mirror {
diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss
index cfdda41619..2d0e3d2a8b 100644
--- a/res/themes/dark/css/_dark.scss
+++ b/res/themes/dark/css/_dark.scss
@@ -9,6 +9,7 @@ $header-panel-text-primary-color: #B9BEC6;
$header-panel-text-secondary-color: #c8c8cd;
$text-primary-color: #ffffff;
$text-secondary-color: #B9BEC6;
+$quaternary-fg-color: #6F7882;
$search-bg-color: #181b21;
$search-placeholder-color: #61708b;
$room-highlight-color: #343a46;
@@ -42,14 +43,6 @@ $preview-bar-bg-color: $header-panel-bg-color;
$groupFilterPanel-bg-color: rgba(38, 39, 43, 0.82);
$inverted-bg-color: $base-color;
-$voice-record-stop-border-color: #6F7882; // "Quarterly"
-$voice-record-waveform-bg-color: #394049; // "Dark Tile"
-$voice-record-waveform-fg-color: $tertiary-fg-color;
-$voice-record-waveform-incomplete-fg-color: #5b646d;
-$voice-record-icon-color: $tertiary-fg-color;
-$voice-playback-button-bg-color: $tertiary-fg-color;
-$voice-playback-button-fg-color: $bg-color;
-
// used by AddressSelector
$selected-color: $room-highlight-color;
@@ -213,9 +206,18 @@ $breadcrumb-placeholder-bg-color: #272c35;
$user-tile-hover-bg-color: $header-panel-bg-color;
-$message-body-panel-bg-color: #21262c82;
-$message-body-panel-icon-bg-color: #8e99a4;
-$message-body-panel-fg-color: $primary-fg-color;
+$message-body-panel-fg-color: $secondary-fg-color;
+$message-body-panel-bg-color: #394049; // "Dark Tile"
+$message-body-panel-icon-fg-color: #21262C; // "Separator"
+$message-body-panel-icon-bg-color: $tertiary-fg-color;
+
+$voice-record-stop-border-color: $quaternary-fg-color;
+$voice-record-waveform-bg-color: $message-body-panel-bg-color;
+$voice-record-waveform-fg-color: $message-body-panel-fg-color;
+$voice-record-waveform-incomplete-fg-color: $quaternary-fg-color;
+$voice-record-icon-color: $quaternary-fg-color;
+$voice-playback-button-bg-color: $message-body-panel-icon-bg-color;
+$voice-playback-button-fg-color: $message-body-panel-icon-fg-color;
// Appearance tab colors
$appearance-tab-border-color: $room-highlight-color;
diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss
index 6413a99ce0..a852ad94e9 100644
--- a/res/themes/legacy-dark/css/_legacy-dark.scss
+++ b/res/themes/legacy-dark/css/_legacy-dark.scss
@@ -124,15 +124,6 @@ $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #3e444c 0%, #3e444c00 100%)
$groupFilterPanel-divider-color: $roomlist-header-color;
-// See non-legacy dark for variable information
-$voice-record-stop-border-color: #6F7882;
-$voice-record-waveform-bg-color: #394049;
-$voice-record-waveform-fg-color: $tertiary-fg-color;
-$voice-record-waveform-incomplete-fg-color: #5b646d;
-$voice-record-icon-color: $tertiary-fg-color;
-$voice-playback-button-bg-color: $tertiary-fg-color;
-$voice-playback-button-fg-color: $bg-color;
-
$roomtile-preview-color: #9e9e9e;
$roomtile-default-badge-bg-color: #61708b;
$roomtile-selected-bg-color: #1A1D23;
@@ -209,9 +200,19 @@ $breadcrumb-placeholder-bg-color: #272c35;
$user-tile-hover-bg-color: $header-panel-bg-color;
-$message-body-panel-bg-color: #21262c82;
-$message-body-panel-icon-bg-color: #8e99a4;
-$message-body-panel-fg-color: $primary-fg-color;
+$message-body-panel-fg-color: $secondary-fg-color;
+$message-body-panel-bg-color: #394049;
+$message-body-panel-icon-fg-color: $primary-bg-color;
+$message-body-panel-icon-bg-color: $secondary-fg-color;
+
+// See non-legacy dark for variable information
+$voice-record-stop-border-color: #6F7882;
+$voice-record-waveform-bg-color: $message-body-panel-bg-color;
+$voice-record-waveform-fg-color: $message-body-panel-fg-color;
+$voice-record-waveform-incomplete-fg-color: #6F7882;
+$voice-record-icon-color: #6F7882;
+$voice-playback-button-bg-color: $tertiary-fg-color;
+$voice-playback-button-fg-color: #21262C;
// Appearance tab colors
$appearance-tab-border-color: $room-highlight-color;
diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss
index 2151724071..84666bc662 100644
--- a/res/themes/legacy-light/css/_legacy-light.scss
+++ b/res/themes/legacy-light/css/_legacy-light.scss
@@ -191,17 +191,6 @@ $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%)
$groupFilterPanel-divider-color: $roomlist-header-color;
-// See non-legacy _light for variable information
-$voice-record-stop-border-color: #E3E8F0;
-$voice-record-stop-symbol-color: #ff4b55;
-$voice-record-waveform-bg-color: #E3E8F0;
-$voice-record-waveform-fg-color: $muted-fg-color;
-$voice-record-waveform-incomplete-fg-color: #C1C6CD;
-$voice-record-live-circle-color: #ff4b55;
-$voice-record-icon-color: $muted-fg-color;
-$voice-playback-button-bg-color: $primary-bg-color;
-$voice-playback-button-fg-color: $muted-fg-color;
-
$roomtile-preview-color: #9e9e9e;
$roomtile-default-badge-bg-color: #61708b;
$roomtile-selected-bg-color: #fff;
@@ -334,9 +323,21 @@ $breadcrumb-placeholder-bg-color: #e8eef5;
$user-tile-hover-bg-color: $header-panel-bg-color;
-$message-body-panel-bg-color: #e3e8f082;
-$message-body-panel-icon-bg-color: #ffffff;
-$message-body-panel-fg-color: $muted-fg-color;
+$message-body-panel-fg-color: $secondary-fg-color;
+$message-body-panel-bg-color: #E3E8F0;
+$message-body-panel-icon-fg-color: $secondary-fg-color;
+$message-body-panel-icon-bg-color: $primary-bg-color;
+
+// See non-legacy _light for variable information
+$voice-record-stop-symbol-color: #ff4b55;
+$voice-record-live-circle-color: #ff4b55;
+$voice-record-stop-border-color: #E3E8F0;
+$voice-record-waveform-bg-color: $message-body-panel-bg-color;
+$voice-record-waveform-fg-color: $message-body-panel-fg-color;
+$voice-record-waveform-incomplete-fg-color: #C1C6CD;
+$voice-record-icon-color: $tertiary-fg-color;
+$voice-playback-button-bg-color: $message-body-panel-icon-bg-color;
+$voice-playback-button-fg-color: $message-body-panel-icon-fg-color;
// FontSlider colors
$appearance-tab-border-color: $input-darker-bg-color;
diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss
index 1763fcdd48..c889f43d0b 100644
--- a/res/themes/light/css/_light.scss
+++ b/res/themes/light/css/_light.scss
@@ -21,6 +21,7 @@ $notice-primary-bg-color: rgba(255, 75, 85, 0.16);
$primary-fg-color: #2e2f32;
$secondary-fg-color: #737D8C;
$tertiary-fg-color: #8D99A5;
+$quaternary-fg-color: #C1C6CD;
$header-panel-bg-color: #f3f8fd;
// typical text (dark-on-white in light skin)
@@ -182,16 +183,6 @@ $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%)
$groupFilterPanel-divider-color: $roomlist-header-color;
-$voice-record-stop-border-color: #E3E8F0;
-$voice-record-stop-symbol-color: #ff4b55; // $warning-color, but without letting people change it in themes
-$voice-record-waveform-bg-color: #E3E8F0;
-$voice-record-waveform-fg-color: $muted-fg-color;
-$voice-record-waveform-incomplete-fg-color: #C1C6CD;
-$voice-record-live-circle-color: #ff4b55; // $warning-color, but without letting people change it in themes
-$voice-record-icon-color: $muted-fg-color;
-$voice-playback-button-bg-color: $primary-bg-color;
-$voice-playback-button-fg-color: $muted-fg-color;
-
$roomtile-preview-color: $secondary-fg-color;
$roomtile-default-badge-bg-color: #61708b;
$roomtile-selected-bg-color: #FFF;
@@ -331,9 +322,23 @@ $breadcrumb-placeholder-bg-color: #e8eef5;
$user-tile-hover-bg-color: $header-panel-bg-color;
-$message-body-panel-bg-color: #e3e8f082;
-$message-body-panel-icon-bg-color: #ffffff;
-$message-body-panel-fg-color: $muted-fg-color;
+$message-body-panel-fg-color: $secondary-fg-color;
+$message-body-panel-bg-color: #E3E8F0; // "Separator"
+$message-body-panel-icon-fg-color: $secondary-fg-color;
+$message-body-panel-icon-bg-color: $primary-bg-color;
+
+// These two don't change between themes. They are the $warning-color, but we don't
+// want custom themes to affect them by accident.
+$voice-record-stop-symbol-color: #ff4b55;
+$voice-record-live-circle-color: #ff4b55;
+
+$voice-record-stop-border-color: #E3E8F0; // "Separator"
+$voice-record-waveform-bg-color: $message-body-panel-bg-color;
+$voice-record-waveform-fg-color: $message-body-panel-fg-color;
+$voice-record-waveform-incomplete-fg-color: $quaternary-fg-color;
+$voice-record-icon-color: $tertiary-fg-color;
+$voice-playback-button-bg-color: $message-body-panel-icon-bg-color;
+$voice-playback-button-fg-color: $message-body-panel-icon-fg-color;
// FontSlider colors
$appearance-tab-border-color: $input-darker-bg-color;
diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts
index 0ab26ef943..e8f2f1bc08 100644
--- a/src/@types/global.d.ts
+++ b/src/@types/global.d.ts
@@ -118,6 +118,16 @@ declare global {
interface HTMLAudioElement {
type?: string;
+ // sinkId & setSinkId are experimental and typescript doesn't know about them
+ sinkId: string;
+ setSinkId(outputId: string);
+ }
+
+ interface HTMLVideoElement {
+ type?: string;
+ // sinkId & setSinkId are experimental and typescript doesn't know about them
+ sinkId: string;
+ setSinkId(outputId: string);
}
interface Element {
diff --git a/src/Avatar.ts b/src/Avatar.ts
index d218ae8b46..a6499c688e 100644
--- a/src/Avatar.ts
+++ b/src/Avatar.ts
@@ -20,6 +20,7 @@ import {Room} from "matrix-js-sdk/src/models/room";
import DMRoomMap from './utils/DMRoomMap';
import {mediaFromMxc} from "./customisations/Media";
+import SettingsStore from "./settings/SettingsStore";
export type ResizeMethod = "crop" | "scale";
@@ -143,7 +144,7 @@ export function avatarUrlForRoom(room: Room, width: number, height: number, resi
}
// space rooms cannot be DMs so skip the rest
- if (room.isSpaceRoom()) return null;
+ if (SettingsStore.getValue("feature_spaces") && room.isSpaceRoom()) return null;
let otherMember = null;
const otherUserId = DMRoomMap.shared().getUserIdForRoomId(room.roomId);
diff --git a/src/CallHandler.tsx b/src/CallHandler.tsx
index 91fd7e4c7d..0268ebfe46 100644
--- a/src/CallHandler.tsx
+++ b/src/CallHandler.tsx
@@ -85,6 +85,7 @@ import { Action } from './dispatcher/actions';
import VoipUserMapper from './VoipUserMapper';
import { addManagedHybridWidget, isManagedHybridWidgetEnabled } from './widgets/ManagedHybrid';
import { randomUppercaseString, randomLowercaseString } from "matrix-js-sdk/src/randomstring";
+import EventEmitter from 'events';
import SdkConfig from './SdkConfig';
import { ensureDMExists, findDMForUser } from './createRoom';
@@ -138,22 +139,12 @@ export enum PlaceCallType {
ScreenSharing = 'screensharing',
}
-function getRemoteAudioElement(): HTMLAudioElement {
- // this needs to be somewhere at the top of the DOM which
- // always exists to avoid audio interruptions.
- // Might as well just use DOM.
- const remoteAudioElement = document.getElementById("remoteAudio") as HTMLAudioElement;
- if (!remoteAudioElement) {
- console.error(
- "Failed to find remoteAudio element - cannot play audio!" +
- "You need to add an to the DOM.",
- );
- return null;
- }
- return remoteAudioElement;
+export enum CallHandlerEvent {
+ CallsChanged = "calls_changed",
+ CallChangeRoom = "call_change_room",
}
-export default class CallHandler {
+export default class CallHandler extends EventEmitter {
private calls = new Map(); // roomId -> call
// Calls started as an attended transfer, ie. with the intention of transferring another
// call with a different party to this one.
@@ -514,6 +505,7 @@ export default class CallHandler {
}
this.calls.set(mappedRoomId, newCall);
+ this.emit(CallHandlerEvent.CallsChanged, this.calls);
this.setCallListeners(newCall);
this.setCallState(newCall, newCall.state);
});
@@ -546,10 +538,7 @@ export default class CallHandler {
this.removeCallForRoom(mappedRoomId);
mappedRoomId = newMappedRoomId;
this.calls.set(mappedRoomId, call);
- dis.dispatch({
- action: Action.CallChangeRoom,
- call,
- });
+ this.emit(CallHandlerEvent.CallChangeRoom, call);
}
}
});
@@ -598,11 +587,6 @@ export default class CallHandler {
}
}
- private setCallAudioElement(call: MatrixCall) {
- const audioElement = getRemoteAudioElement();
- if (audioElement) call.setRemoteAudioElement(audioElement);
- }
-
private setCallState(call: MatrixCall, status: CallState) {
const mappedRoomId = CallHandler.sharedInstance().roomIdForCall(call);
@@ -619,6 +603,7 @@ export default class CallHandler {
private removeCallForRoom(roomId: string) {
this.calls.delete(roomId);
+ this.emit(CallHandlerEvent.CallsChanged, this.calls);
}
private showICEFallbackPrompt() {
@@ -679,11 +664,7 @@ export default class CallHandler {
}, null, true);
}
- private async placeCall(
- roomId: string, type: PlaceCallType,
- localElement: HTMLVideoElement, remoteElement: HTMLVideoElement,
- transferee: MatrixCall,
- ) {
+ private async placeCall(roomId: string, type: PlaceCallType, transferee: MatrixCall) {
Analytics.trackEvent('voip', 'placeCall', 'type', type);
CountlyAnalytics.instance.trackStartCall(roomId, type === PlaceCallType.Video, false);
@@ -695,22 +676,19 @@ export default class CallHandler {
const call = MatrixClientPeg.get().createCall(mappedRoomId);
this.calls.set(roomId, call);
+ this.emit(CallHandlerEvent.CallsChanged, this.calls);
if (transferee) {
this.transferees[call.callId] = transferee;
}
this.setCallListeners(call);
- this.setCallAudioElement(call);
this.setActiveCallRoomId(roomId);
if (type === PlaceCallType.Voice) {
call.placeVoiceCall();
} else if (type === 'video') {
- call.placeVideoCall(
- remoteElement,
- localElement,
- );
+ call.placeVideoCall();
} else if (type === PlaceCallType.ScreenSharing) {
const screenCapErrorString = PlatformPeg.get().screenCaptureErrorString();
if (screenCapErrorString) {
@@ -724,13 +702,12 @@ export default class CallHandler {
}
call.placeScreenSharingCall(
- remoteElement,
- localElement,
async (): Promise => {
const {finished} = Modal.createDialog(DesktopCapturerSourcePicker);
const [source] = await finished;
return source;
- });
+ },
+ );
} else {
console.error("Unknown conf call type: " + type);
}
@@ -787,17 +764,12 @@ export default class CallHandler {
} else if (members.length === 2) {
console.info(`Place ${payload.type} call in ${payload.room_id}`);
- this.placeCall(
- payload.room_id, payload.type, payload.local_element, payload.remote_element,
- payload.transferee,
- );
+ this.placeCall(payload.room_id, payload.type, payload.transferee);
} else { // > 2
dis.dispatch({
action: "place_conference_call",
room_id: payload.room_id,
type: payload.type,
- remote_element: payload.remote_element,
- local_element: payload.local_element,
});
}
}
@@ -833,6 +805,7 @@ export default class CallHandler {
Analytics.trackEvent('voip', 'receiveCall', 'type', call.type);
this.calls.set(mappedRoomId, call)
+ this.emit(CallHandlerEvent.CallsChanged, this.calls);
this.setCallListeners(call);
// get ready to send encrypted events in the room, so if the user does answer
@@ -875,7 +848,6 @@ export default class CallHandler {
const call = this.calls.get(payload.room_id);
call.answer();
- this.setCallAudioElement(call);
this.setActiveCallRoomId(payload.room_id);
CountlyAnalytics.instance.trackJoinCall(payload.room_id, call.type === CallType.Video, false);
dis.dispatch({
diff --git a/src/CallMediaHandler.js b/src/CallMediaHandler.js
index 7c7940cab5..634f0bb336 100644
--- a/src/CallMediaHandler.js
+++ b/src/CallMediaHandler.js
@@ -16,7 +16,7 @@
import SettingsStore from "./settings/SettingsStore";
import {SettingLevel} from "./settings/SettingLevel";
-import {setMatrixCallAudioInput, setMatrixCallAudioOutput, setMatrixCallVideoInput} from "matrix-js-sdk/src/matrix";
+import {setMatrixCallAudioInput, setMatrixCallVideoInput} from "matrix-js-sdk/src/matrix";
export default {
hasAnyLabeledDevices: async function() {
@@ -50,18 +50,15 @@ export default {
},
loadDevices: function() {
- const audioOutDeviceId = SettingsStore.getValue("webrtc_audiooutput");
const audioDeviceId = SettingsStore.getValue("webrtc_audioinput");
const videoDeviceId = SettingsStore.getValue("webrtc_videoinput");
- setMatrixCallAudioOutput(audioOutDeviceId);
setMatrixCallAudioInput(audioDeviceId);
setMatrixCallVideoInput(videoDeviceId);
},
setAudioOutput: function(deviceId) {
SettingsStore.setValue("webrtc_audiooutput", null, SettingLevel.DEVICE, deviceId);
- setMatrixCallAudioOutput(deviceId);
},
setAudioInput: function(deviceId) {
diff --git a/src/SlashCommands.tsx b/src/SlashCommands.tsx
index 6ce1439164..4a7b37b5e5 100644
--- a/src/SlashCommands.tsx
+++ b/src/SlashCommands.tsx
@@ -38,7 +38,7 @@ import {isPermalinkHost, parsePermalink} from "./utils/permalinks/Permalinks";
import {inviteUsersToRoom} from "./RoomInvite";
import { WidgetType } from "./widgets/WidgetType";
import { Jitsi } from "./widgets/Jitsi";
-import { parseFragment as parseHtml } from "parse5";
+import { parseFragment as parseHtml, Element as ChildElement } from "parse5";
import BugReportDialog from "./components/views/dialogs/BugReportDialog";
import { ensureDMExists } from "./createRoom";
import { ViewUserPayload } from "./dispatcher/payloads/ViewUserPayload";
@@ -856,7 +856,7 @@ export const Commands = [
// some superfast regex over the text so we don't have to.
const embed = parseHtml(widgetUrl);
if (embed && embed.childNodes && embed.childNodes.length === 1) {
- const iframe = embed.childNodes[0];
+ const iframe = embed.childNodes[0] as ChildElement;
if (iframe.tagName.toLowerCase() === 'iframe' && iframe.attrs) {
const srcAttr = iframe.attrs.find(a => a.name === 'src');
console.log("Pulling URL out of iframe (embed code)");
diff --git a/src/components/structures/LoggedInView.tsx b/src/components/structures/LoggedInView.tsx
index 0255a3bf35..c4b9696807 100644
--- a/src/components/structures/LoggedInView.tsx
+++ b/src/components/structures/LoggedInView.tsx
@@ -59,6 +59,9 @@ import { getKeyBindingsManager, NavigationAction, RoomAction } from '../../KeyBi
import { IOpts } from "../../createRoom";
import SpacePanel from "../views/spaces/SpacePanel";
import {replaceableComponent} from "../../utils/replaceableComponent";
+import CallHandler, { CallHandlerEvent } from '../../CallHandler';
+import { MatrixCall } from 'matrix-js-sdk/src/webrtc/call';
+import AudioFeedArrayForCall from '../views/voip/AudioFeedArrayForCall';
// We need to fetch each pinned message individually (if we don't already have it)
// so each pinned message may trigger a request. Limit the number per room for sanity.
@@ -119,6 +122,7 @@ interface IState {
usageLimitEventContent?: IUsageLimit;
usageLimitEventTs?: number;
useCompactLayout: boolean;
+ activeCalls: Array;
}
/**
@@ -160,6 +164,7 @@ class LoggedInView extends React.Component {
// use compact timeline view
useCompactLayout: SettingsStore.getValue('useCompactLayout'),
usageLimitDismissed: false,
+ activeCalls: [],
};
// stash the MatrixClient in case we log out before we are unmounted
@@ -175,6 +180,7 @@ class LoggedInView extends React.Component {
componentDidMount() {
document.addEventListener('keydown', this._onNativeKeyDown, false);
+ CallHandler.sharedInstance().addListener(CallHandlerEvent.CallsChanged, this.onCallsChanged);
this._updateServerNoticeEvents();
@@ -199,6 +205,7 @@ class LoggedInView extends React.Component {
componentWillUnmount() {
document.removeEventListener('keydown', this._onNativeKeyDown, false);
+ CallHandler.sharedInstance().removeListener(CallHandlerEvent.CallsChanged, this.onCallsChanged);
this._matrixClient.removeListener("accountData", this.onAccountData);
this._matrixClient.removeListener("sync", this.onSync);
this._matrixClient.removeListener("RoomState.events", this.onRoomStateEvents);
@@ -206,6 +213,12 @@ class LoggedInView extends React.Component {
this.resizer.detach();
}
+ private onCallsChanged = () => {
+ this.setState({
+ activeCalls: CallHandler.sharedInstance().getAllActiveCalls(),
+ });
+ };
+
// Child components assume that the client peg will not be null, so give them some
// sort of assurance here by only allowing a re-render if the client is truthy.
//
@@ -661,6 +674,12 @@ class LoggedInView extends React.Component {
bodyClasses += ' mx_MatrixChat_useCompactLayout';
}
+ const audioFeedArraysForCalls = this.state.activeCalls.map((call) => {
+ return (
+
+ );
+ });
+
return (
{
+ {audioFeedArraysForCalls}
);
}
diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx
index 078b296295..e330dc7d38 100644
--- a/src/components/structures/MatrixChat.tsx
+++ b/src/components/structures/MatrixChat.tsx
@@ -1094,7 +1094,7 @@ export default class MatrixChat extends React.PureComponent {
private leaveRoomWarnings(roomId: string) {
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
- const isSpace = roomToLeave?.isSpaceRoom();
+ const isSpace = SettingsStore.getValue("feature_spaces") && roomToLeave?.isSpaceRoom();
// Show a warning if there are additional complications.
const warnings = [];
@@ -1133,7 +1133,7 @@ export default class MatrixChat extends React.PureComponent {
const roomToLeave = MatrixClientPeg.get().getRoom(roomId);
const warnings = this.leaveRoomWarnings(roomId);
- const isSpace = roomToLeave?.isSpaceRoom();
+ const isSpace = SettingsStore.getValue("feature_spaces") && roomToLeave?.isSpaceRoom();
Modal.createTrackedDialog(isSpace ? "Leave space" : "Leave room", '', QuestionDialog, {
title: isSpace ? _t("Leave space") : _t("Leave room"),
description: (
diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js
index c93f07fa0f..e5e0065be8 100644
--- a/src/components/structures/MessagePanel.js
+++ b/src/components/structures/MessagePanel.js
@@ -544,11 +544,13 @@ export default class MessagePanel extends React.Component {
}
if (!grouper) {
const wantTile = this._shouldShowEvent(mxEv);
+ const isGrouped = false;
if (wantTile) {
// make sure we unpack the array returned by _getTilesForEvent,
// otherwise react will auto-generate keys and we will end up
// replacing all of the DOM elements every time we paginate.
- ret.push(...this._getTilesForEvent(prevEvent, mxEv, last, nextEvent, nextTile));
+ ret.push(...this._getTilesForEvent(prevEvent, mxEv, last, isGrouped,
+ nextEvent, nextTile));
prevEvent = mxEv;
}
@@ -564,7 +566,7 @@ export default class MessagePanel extends React.Component {
return ret;
}
- _getTilesForEvent(prevEvent, mxEv, last, nextEvent, nextEventWithTile) {
+ _getTilesForEvent(prevEvent, mxEv, last, isGrouped=false, nextEvent, nextEventWithTile) {
const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary');
const EventTile = sdk.getComponent('rooms.EventTile');
const DateSeparator = sdk.getComponent('messages.DateSeparator');
@@ -584,7 +586,7 @@ export default class MessagePanel extends React.Component {
// do we need a date separator since the last event?
const wantsDateSeparator = this._wantsDateSeparator(prevEvent, eventDate);
- if (wantsDateSeparator) {
+ if (wantsDateSeparator && !isGrouped) {
const dateSeparator =
;
ret.push(dateSeparator);
}
@@ -968,9 +970,9 @@ class CreationGrouper {
const DateSeparator = sdk.getComponent('messages.DateSeparator');
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
-
const panel = this.panel;
const ret = [];
+ const isGrouped = true;
const createEvent = this.createEvent;
const lastShownEvent = this.lastShownEvent;
@@ -984,12 +986,12 @@ class CreationGrouper {
// If this m.room.create event should be shown (room upgrade) then show it before the summary
if (panel._shouldShowEvent(createEvent)) {
// pass in the createEvent as prevEvent as well so no extra DateSeparator is rendered
- ret.push(...panel._getTilesForEvent(createEvent, createEvent, false));
+ ret.push(...panel._getTilesForEvent(createEvent, createEvent));
}
for (const ejected of this.ejectedEvents) {
ret.push(...panel._getTilesForEvent(
- createEvent, ejected, createEvent === lastShownEvent,
+ createEvent, ejected, createEvent === lastShownEvent, isGrouped,
));
}
@@ -998,7 +1000,7 @@ class CreationGrouper {
// of EventListSummary, render each member event as if the previous
// one was itself. This way, the timestamp of the previous event === the
// timestamp of the current event, and no DateSeparator is inserted.
- return panel._getTilesForEvent(e, e, e === lastShownEvent);
+ return panel._getTilesForEvent(e, e, e === lastShownEvent, isGrouped);
}).reduce((a, b) => a.concat(b), []);
// Get sender profile from the latest event in the summary as the m.room.create doesn't contain one
const ev = this.events[this.events.length - 1];
@@ -1083,7 +1085,7 @@ class RedactionGrouper {
const DateSeparator = sdk.getComponent('messages.DateSeparator');
const EventListSummary = sdk.getComponent('views.elements.EventListSummary');
-
+ const isGrouped = true;
const panel = this.panel;
const ret = [];
const lastShownEvent = this.lastShownEvent;
@@ -1103,7 +1105,8 @@ class RedactionGrouper {
let eventTiles = this.events.map((e, i) => {
senders.add(e.sender);
const prevEvent = i === 0 ? this.prevEvent : this.events[i - 1];
- return panel._getTilesForEvent(prevEvent, e, e === lastShownEvent, this.nextEvent, this.nextEventTile);
+ return panel._getTilesForEvent(
+ prevEvent, e, e === lastShownEvent, isGrouped, this.nextEvent, this.nextEventTile);
}).reduce((a, b) => a.concat(b), []);
if (eventTiles.length === 0) {
@@ -1182,7 +1185,7 @@ class MemberGrouper {
const DateSeparator = sdk.getComponent('messages.DateSeparator');
const MemberEventListSummary = sdk.getComponent('views.elements.MemberEventListSummary');
-
+ const isGrouped = true;
const panel = this.panel;
const lastShownEvent = this.lastShownEvent;
const ret = [];
@@ -1215,7 +1218,7 @@ class MemberGrouper {
// of MemberEventListSummary, render each member event as if the previous
// one was itself. This way, the timestamp of the previous event === the
// timestamp of the current event, and no DateSeparator is inserted.
- return panel._getTilesForEvent(e, e, e === lastShownEvent);
+ return panel._getTilesForEvent(e, e, e === lastShownEvent, isGrouped);
}).reduce((a, b) => a.concat(b), []);
if (eventTiles.length === 0) {
diff --git a/src/components/structures/RightPanel.js b/src/components/structures/RightPanel.js
index 5bcb3b2450..d8c763eabd 100644
--- a/src/components/structures/RightPanel.js
+++ b/src/components/structures/RightPanel.js
@@ -35,6 +35,7 @@ import {Action} from "../../dispatcher/actions";
import RoomSummaryCard from "../views/right_panel/RoomSummaryCard";
import WidgetCard from "../views/right_panel/WidgetCard";
import {replaceableComponent} from "../../utils/replaceableComponent";
+import SettingsStore from "../../settings/SettingsStore";
@replaceableComponent("structures.RightPanel")
export default class RightPanel extends React.Component {
@@ -85,7 +86,9 @@ export default class RightPanel extends React.Component {
return RightPanelPhases.GroupMemberList;
}
return rps.groupPanelPhase;
- } else if (this.props.room?.isSpaceRoom() && !RIGHT_PANEL_SPACE_PHASES.includes(rps.roomPanelPhase)) {
+ } else if (SettingsStore.getValue("feature_spaces") && this.props.room?.isSpaceRoom()
+ && !RIGHT_PANEL_SPACE_PHASES.includes(rps.roomPanelPhase)
+ ) {
return RightPanelPhases.SpaceMemberList;
} else if (userForPanel) {
// XXX FIXME AAAAAARGH: What is going on with this class!? It takes some of its state
diff --git a/src/components/structures/RoomSearch.tsx b/src/components/structures/RoomSearch.tsx
index 34682877e0..bda46aef07 100644
--- a/src/components/structures/RoomSearch.tsx
+++ b/src/components/structures/RoomSearch.tsx
@@ -27,8 +27,8 @@ import { Action } from "../../dispatcher/actions";
import RoomListStore from "../../stores/room-list/RoomListStore";
import { NameFilterCondition } from "../../stores/room-list/filters/NameFilterCondition";
import { getKeyBindingsManager, RoomListAction } from "../../KeyBindingsManager";
-import {replaceableComponent} from "../../utils/replaceableComponent";
-import SpaceStore, {UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES} from "../../stores/SpaceStore";
+import { replaceableComponent } from "../../utils/replaceableComponent";
+import SpaceStore, { UPDATE_SELECTED_SPACE, UPDATE_TOP_LEVEL_SPACES } from "../../stores/SpaceStore";
interface IProps {
isMinimized: boolean;
diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx
index 5108643673..58a87e6641 100644
--- a/src/components/structures/RoomView.tsx
+++ b/src/components/structures/RoomView.tsx
@@ -1750,7 +1750,10 @@ export default class RoomView extends React.Component {
}
const myMembership = this.state.room.getMyMembership();
- if (myMembership === "invite" && !this.state.room.isSpaceRoom()) { // SpaceRoomView handles invites itself
+ if (myMembership === "invite"
+ // SpaceRoomView handles invites itself
+ && (!SettingsStore.getValue("feature_spaces") || !this.state.room.isSpaceRoom())
+ ) {
if (this.state.joining || this.state.rejecting) {
return (
@@ -1892,7 +1895,7 @@ export default class RoomView extends React.Component {
room={this.state.room}
/>
);
- if (!this.state.canPeek && !this.state.room?.isSpaceRoom()) {
+ if (!this.state.canPeek && (!SettingsStore.getValue("feature_spaces") || !this.state.room?.isSpaceRoom())) {
return (
{ previewBar }
diff --git a/src/components/structures/SpaceRoomDirectory.tsx b/src/components/structures/SpaceRoomDirectory.tsx
index 8d6c9f0a70..74415cc58f 100644
--- a/src/components/structures/SpaceRoomDirectory.tsx
+++ b/src/components/structures/SpaceRoomDirectory.tsx
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import React, {useMemo, useState} from "react";
+import React, {ReactNode, useMemo, useState} from "react";
import {Room} from "matrix-js-sdk/src/models/room";
import {MatrixClient} from "matrix-js-sdk/src/client";
import {EventType, RoomType} from "matrix-js-sdk/src/@types/event";
@@ -24,7 +24,7 @@ import {sortBy} from "lodash";
import {MatrixClientPeg} from "../../MatrixClientPeg";
import dis from "../../dispatcher/dispatcher";
import {_t} from "../../languageHandler";
-import AccessibleButton from "../views/elements/AccessibleButton";
+import AccessibleButton, {ButtonEvent} from "../views/elements/AccessibleButton";
import BaseDialog from "../views/dialogs/BaseDialog";
import Spinner from "../views/elements/Spinner";
import SearchBox from "./SearchBox";
@@ -39,11 +39,14 @@ import {mediaFromMxc} from "../../customisations/Media";
import InfoTooltip from "../views/elements/InfoTooltip";
import TextWithTooltip from "../views/elements/TextWithTooltip";
import {useStateToggle} from "../../hooks/useStateToggle";
+import {getOrder} from "../../stores/SpaceStore";
+import AccessibleTooltipButton from "../views/elements/AccessibleTooltipButton";
interface IHierarchyProps {
space: Room;
initialText?: string;
refreshToken?: any;
+ additionalButtons?: ReactNode;
showRoom(room: ISpaceSummaryRoom, viaServers?: string[], autoJoin?: boolean): void;
}
@@ -106,8 +109,16 @@ const Tile: React.FC = ({
const cliRoom = cli.getRoom(room.room_id);
const myMembership = cliRoom?.getMyMembership();
- const onPreviewClick = () => onViewRoomClick(false);
- const onJoinClick = () => onViewRoomClick(true);
+ const onPreviewClick = (ev: ButtonEvent) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+ onViewRoomClick(false);
+ }
+ const onJoinClick = (ev: ButtonEvent) => {
+ ev.preventDefault();
+ ev.stopPropagation();
+ onViewRoomClick(true);
+ }
let button;
if (myMembership === "join") {
@@ -254,7 +265,11 @@ export const HierarchyLevel = ({
const space = cli.getRoom(spaceId);
const hasPermissions = space?.currentState.maySendStateEvent(EventType.SpaceChild, cli.getUserId());
- const sortedChildren = sortBy([...(relations.get(spaceId)?.values() || [])], ev => ev.content.order || null);
+ const children = Array.from(relations.get(spaceId)?.values() || []);
+ const sortedChildren = sortBy(children, ev => {
+ // XXX: Space Summary API doesn't give the child origin_server_ts but once it does we should use it for sorting
+ return getOrder(ev.content.order, null, ev.state_key);
+ });
const [subspaces, childRooms] = sortedChildren.reduce((result, ev: ISpaceSummaryEvent) => {
const roomId = ev.state_key;
if (!rooms.has(roomId)) return result;
@@ -350,6 +365,7 @@ export const SpaceHierarchy: React.FC = ({
initialText = "",
showRoom,
refreshToken,
+ additionalButtons,
children,
}) => {
const cli = MatrixClientPeg.get();
@@ -415,78 +431,83 @@ export const SpaceHierarchy: React.FC = ({
countsStr = _t("%(count)s rooms", { count: numRooms, numSpaces });
}
- let editSection;
+ let manageButtons;
if (space.getMyMembership() === "join" && space.currentState.maySendStateEvent(EventType.SpaceChild, userId)) {
const selectedRelations = Array.from(selected.keys()).flatMap(parentId => {
return [...selected.get(parentId).values()].map(childId => [parentId, childId]) as [string, string][];
});
- let buttons;
- if (selectedRelations.length) {
- const selectionAllSuggested = selectedRelations.every(([parentId, childId]) => {
- return parentChildMap.get(parentId)?.get(childId)?.content.suggested;
- });
+ const selectionAllSuggested = selectedRelations.every(([parentId, childId]) => {
+ return parentChildMap.get(parentId)?.get(childId)?.content.suggested;
+ });
- const disabled = removing || saving;
+ const disabled = !selectedRelations.length || removing || saving;
- buttons = <>
- {
- setRemoving(true);
- try {
- for (const [parentId, childId] of selectedRelations) {
- await cli.sendStateEvent(parentId, EventType.SpaceChild, {}, childId);
- parentChildMap.get(parentId).get(childId).content = {};
- parentChildMap.set(parentId, new Map(parentChildMap.get(parentId)));
- }
- } catch (e) {
- setError(_t("Failed to remove some rooms. Try again later"));
- }
- setRemoving(false);
- }}
- kind="danger_outline"
- disabled={disabled}
- >
- { removing ? _t("Removing...") : _t("Remove") }
-
- {
- setSaving(true);
- try {
- for (const [parentId, childId] of selectedRelations) {
- const suggested = !selectionAllSuggested;
- const existingContent = parentChildMap.get(parentId)?.get(childId)?.content;
- if (!existingContent || existingContent.suggested === suggested) continue;
-
- const content = {
- ...existingContent,
- suggested: !selectionAllSuggested,
- };
-
- await cli.sendStateEvent(parentId, EventType.SpaceChild, content, childId);
-
- parentChildMap.get(parentId).get(childId).content = content;
- parentChildMap.set(parentId, new Map(parentChildMap.get(parentId)));
- }
- } catch (e) {
- setError("Failed to update some suggestions. Try again later");
- }
- setSaving(false);
- }}
- kind="primary_outline"
- disabled={disabled}
- >
- { saving
- ? _t("Saving...")
- : (selectionAllSuggested ? _t("Mark as not suggested") : _t("Mark as suggested"))
- }
-
- >;
+ let Button: React.ComponentType> = AccessibleButton;
+ let props = {};
+ if (!selectedRelations.length) {
+ Button = AccessibleTooltipButton;
+ props = {
+ tooltip: _t("Select a room below first"),
+ yOffset: -40,
+ };
}
- editSection =
- { buttons }
- ;
+ manageButtons = <>
+
+
+ >;
}
let results;
@@ -532,7 +553,10 @@ export const SpaceHierarchy: React.FC = ({
content = <>